<template><div><h1 id="第-10-章-简化条件逻辑" tabindex="-1"><a class="header-anchor" href="#第-10-章-简化条件逻辑"><span>第 10 章 简化条件逻辑</span></a></h1>
<p>程序的大部分威力来自条件逻辑，但很不幸，程序的复杂度也大多来自条件逻辑。我经常借助重构把条件逻辑变得更容易理解。我常用分解条件表达式（260）处理复杂的条件表达式，用合并条件表达式（263）厘清逻辑组合。我会用以卫语句取代嵌套条件表达式（266）清晰表达“在主要处理逻辑之前先做检查”的意图。如果我发现一处 switch 逻辑处理了几种情况，可以考虑拿出以多态取代条件表达式（272）重构手法。</p>
<p>很多条件逻辑是用于处理特殊情况的，例如处理 null 值。如果对某种特殊情况的处理逻辑大多相同，那么可以用引入特例（289）（常被称作引入空对象）消除重复代码。另外，虽然我很喜欢去除条件逻辑，但如果我想明确地表述（以及检查）程序的状态，引入断言（302）是一个不错的补充。</p>
<h2 id="_10-1-分解条件表达式-decompose-conditional" tabindex="-1"><a class="header-anchor" href="#_10-1-分解条件表达式-decompose-conditional"><span>10.1 分解条件表达式（Decompose Conditional）</span></a></h2>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>aDate<span class="token punctuation">.</span><span class="token function">isBefore</span><span class="token punctuation">(</span>plan<span class="token punctuation">.</span>summerStart<span class="token punctuation">)</span> <span class="token operator">&amp;</span>amp<span class="token punctuation">;</span><span class="token operator">&amp;</span>amp<span class="token punctuation">;</span> <span class="token operator">!</span>aDate<span class="token punctuation">.</span><span class="token function">isAfter</span><span class="token punctuation">(</span>plan<span class="token punctuation">.</span>summerEnd<span class="token punctuation">)</span><span class="token punctuation">)</span></span>
<span class="line"> charge <span class="token operator">=</span> quantity <span class="token operator">*</span> plan<span class="token punctuation">.</span>summerRate<span class="token punctuation">;</span></span>
<span class="line"><span class="token keyword">else</span></span>
<span class="line"> charge <span class="token operator">=</span> quantity <span class="token operator">*</span> plan<span class="token punctuation">.</span>regularRate <span class="token operator">+</span> plan<span class="token punctuation">.</span>regularServiceCharge<span class="token punctuation">;</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">summer</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span></span>
<span class="line"> charge <span class="token operator">=</span> <span class="token function">summerCharge</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token keyword">else</span></span>
<span class="line"> charge <span class="token operator">=</span> <span class="token function">regularCharge</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3 id="动机" tabindex="-1"><a class="header-anchor" href="#动机"><span>动机</span></a></h3>
<p>程序之中，复杂的条件逻辑是最常导致复杂度上升的地点之一。我必须编写代码来检查不同的条件分支，根据不同的条件做不同的事，然后，我很快就会得到一个相当长的函数。大型函数本身就会使代码的可读性下降，而条件逻辑则会使代码更难阅读。在带有复杂条件逻辑的函数中，代码（包括检查条件分支的代码和真正实现功能的代码）会告诉我发生的事，但常常让我弄不清楚为什么会发生这样的事，这就说明代码的可读性的确大大降低了。</p>
<p>和任何大块头代码一样，我可以将它分解为多个独立的函数，根据每个小块代码的用途，为分解而得的新函数命名，并将原函数中对应的代码改为调用新函数，从而更清楚地表达自己的意图。对于条件逻辑，将每个分支条件分解成新函数还可以带来更多好处：可以突出条件逻辑，更清楚地表明每个分支的作用，并且突出每个分支的原因。</p>
<p>本重构手法其实只是提炼函数（106）的一个应用场景。但我要特别强调这个场景，因为我发现它经常会带来很大的价值。</p>
<h3 id="做法" tabindex="-1"><a class="header-anchor" href="#做法"><span>做法</span></a></h3>
<p>对条件判断和每个条件分支分别运用提炼函数（106）手法。</p>
<h3 id="范例" tabindex="-1"><a class="header-anchor" href="#范例"><span>范例</span></a></h3>
<p>假设我要计算购买某样商品的总价（总价=数量 × 单价），而这个商品在冬季和夏季的单价是不同的：</p>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>aDate<span class="token punctuation">.</span><span class="token function">isBefore</span><span class="token punctuation">(</span>plan<span class="token punctuation">.</span>summerStart<span class="token punctuation">)</span> <span class="token operator">&amp;</span>amp<span class="token punctuation">;</span><span class="token operator">&amp;</span>amp<span class="token punctuation">;</span> <span class="token operator">!</span>aDate<span class="token punctuation">.</span><span class="token function">isAfter</span><span class="token punctuation">(</span>plan<span class="token punctuation">.</span>summerEnd<span class="token punctuation">)</span><span class="token punctuation">)</span></span>
<span class="line"> charge <span class="token operator">=</span> quantity <span class="token operator">*</span> plan<span class="token punctuation">.</span>summerRate<span class="token punctuation">;</span></span>
<span class="line"><span class="token keyword">else</span></span>
<span class="line"> charge <span class="token operator">=</span> quantity <span class="token operator">*</span> plan<span class="token punctuation">.</span>regularRate <span class="token operator">+</span> plan<span class="token punctuation">.</span>regularServiceCharge<span class="token punctuation">;</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>我把条件判断提炼到一个独立的函数中：</p>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">summer</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span></span>
<span class="line"> charge <span class="token operator">=</span> quantity <span class="token operator">*</span> plan<span class="token punctuation">.</span>summerRate<span class="token punctuation">;</span></span>
<span class="line"><span class="token keyword">else</span></span>
<span class="line"> charge <span class="token operator">=</span> quantity <span class="token operator">*</span> plan<span class="token punctuation">.</span>regularRate <span class="token operator">+</span> plan<span class="token punctuation">.</span>regularServiceCharge<span class="token punctuation">;</span></span>
<span class="line"></span>
<span class="line"><span class="token keyword">function</span> <span class="token function">summer</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">return</span> <span class="token operator">!</span>aDate<span class="token punctuation">.</span><span class="token function">isBefore</span><span class="token punctuation">(</span>plan<span class="token punctuation">.</span>summerStart<span class="token punctuation">)</span> <span class="token operator">&amp;</span>amp<span class="token punctuation">;</span><span class="token operator">&amp;</span>amp<span class="token punctuation">;</span> <span class="token operator">!</span>aDate<span class="token punctuation">.</span><span class="token function">isAfter</span><span class="token punctuation">(</span>plan<span class="token punctuation">.</span>summerEnd<span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>然后提炼条件判断为真的分支：</p>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">summer</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span></span>
<span class="line"> charge <span class="token operator">=</span> <span class="token function">summerCharge</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token keyword">else</span></span>
<span class="line"> charge <span class="token operator">=</span> quantity <span class="token operator">*</span> plan<span class="token punctuation">.</span>regularRate <span class="token operator">+</span> plan<span class="token punctuation">.</span>regularServiceCharge<span class="token punctuation">;</span></span>
<span class="line"></span>
<span class="line"><span class="token keyword">function</span> <span class="token function">summer</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">return</span> <span class="token operator">!</span>aDate<span class="token punctuation">.</span><span class="token function">isBefore</span><span class="token punctuation">(</span>plan<span class="token punctuation">.</span>summerStart<span class="token punctuation">)</span> <span class="token operator">&amp;</span>amp<span class="token punctuation">;</span><span class="token operator">&amp;</span>amp<span class="token punctuation">;</span> <span class="token operator">!</span>aDate<span class="token punctuation">.</span><span class="token function">isAfter</span><span class="token punctuation">(</span>plan<span class="token punctuation">.</span>summerEnd<span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"><span class="token keyword">function</span> <span class="token function">summerCharge</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">return</span> quantity <span class="token operator">*</span> plan<span class="token punctuation">.</span>summerRate<span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>最后提炼条件判断为假的分支：</p>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">summer</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span></span>
<span class="line"> charge <span class="token operator">=</span> <span class="token function">summerCharge</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token keyword">else</span></span>
<span class="line"> charge <span class="token operator">=</span> <span class="token function">regularCharge</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"></span>
<span class="line"><span class="token keyword">function</span> <span class="token function">summer</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">return</span> <span class="token operator">!</span>aDate<span class="token punctuation">.</span><span class="token function">isBefore</span><span class="token punctuation">(</span>plan<span class="token punctuation">.</span>summerStart<span class="token punctuation">)</span> <span class="token operator">&amp;</span>amp<span class="token punctuation">;</span><span class="token operator">&amp;</span>amp<span class="token punctuation">;</span> <span class="token operator">!</span>aDate<span class="token punctuation">.</span><span class="token function">isAfter</span><span class="token punctuation">(</span>plan<span class="token punctuation">.</span>summerEnd<span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"><span class="token keyword">function</span> <span class="token function">summerCharge</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">return</span> quantity <span class="token operator">*</span> plan<span class="token punctuation">.</span>summerRate<span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"><span class="token keyword">function</span> <span class="token function">regularCharge</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">return</span> quantity <span class="token operator">*</span> plan<span class="token punctuation">.</span>regularRate <span class="token operator">+</span> plan<span class="token punctuation">.</span>regularServiceCharge<span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>提炼完成后，我喜欢用三元运算符重新安排条件语句。</p>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line">  charge <span class="token operator">=</span> <span class="token function">summer</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">?</span> <span class="token function">summerCharge</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">:</span> <span class="token function">regularCharge</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"></span>
<span class="line"><span class="token keyword">function</span> <span class="token function">summer</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">return</span> <span class="token operator">!</span>aDate<span class="token punctuation">.</span><span class="token function">isBefore</span><span class="token punctuation">(</span>plan<span class="token punctuation">.</span>summerStart<span class="token punctuation">)</span> <span class="token operator">&amp;</span>amp<span class="token punctuation">;</span><span class="token operator">&amp;</span>amp<span class="token punctuation">;</span> <span class="token operator">!</span>aDate<span class="token punctuation">.</span><span class="token function">isAfter</span><span class="token punctuation">(</span>plan<span class="token punctuation">.</span>summerEnd<span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"><span class="token keyword">function</span> <span class="token function">summerCharge</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">return</span> quantity <span class="token operator">*</span> plan<span class="token punctuation">.</span>summerRate<span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"><span class="token keyword">function</span> <span class="token function">regularCharge</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">return</span> quantity <span class="token operator">*</span> plan<span class="token punctuation">.</span>regularRate <span class="token operator">+</span> plan<span class="token punctuation">.</span>regularServiceCharge<span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2 id="_10-2-合并条件表达式-consolidate-conditional-expression" tabindex="-1"><a class="header-anchor" href="#_10-2-合并条件表达式-consolidate-conditional-expression"><span>10.2 合并条件表达式（Consolidate Conditional Expression）</span></a></h2>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">if</span> <span class="token punctuation">(</span>anEmployee<span class="token punctuation">.</span>seniority <span class="token operator">&lt;</span> <span class="token number">2</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token keyword">if</span> <span class="token punctuation">(</span>anEmployee<span class="token punctuation">.</span>monthsDisabled <span class="token operator">></span> <span class="token number">12</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token keyword">if</span> <span class="token punctuation">(</span>anEmployee<span class="token punctuation">.</span>isPartTime<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">isNotEligibleForDisability</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span></span>
<span class="line"></span>
<span class="line"><span class="token keyword">function</span> <span class="token function">isNotEligibleForDisability</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>anEmployee<span class="token punctuation">.</span>seniority <span class="token operator">&lt;</span> <span class="token number">2</span><span class="token punctuation">)</span></span>
<span class="line">     <span class="token operator">||</span> <span class="token punctuation">(</span>anEmployee<span class="token punctuation">.</span>monthsDisabled <span class="token operator">></span> <span class="token number">12</span><span class="token punctuation">)</span></span>
<span class="line">     <span class="token operator">||</span> <span class="token punctuation">(</span>anEmployee<span class="token punctuation">.</span>isPartTime<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3 id="动机-1" tabindex="-1"><a class="header-anchor" href="#动机-1"><span>动机</span></a></h3>
<p>有时我会发现这样一串条件检查：检查条件各不相同，最终行为却一致。如果发现这种情况，就应该使用“逻辑或”和“逻辑与”将它们合并为一个条件表达式。</p>
<p>之所以要合并条件代码，有两个重要原因。首先，合并后的条件代码会表述“实际上只有一次条件检查，只不过有多个并列条件需要检查而已”，从而使这一次检查的用意更清晰。当然，合并前和合并后的代码有着相同的效果，但原先代码传达出的信息却是“这里有一些各自独立的条件测试，它们只是恰好同时发生”。其次，这项重构往往可以为使用提炼函数（106）做好准备。将检查条件提炼成一个独立的函数对于厘清代码意义非常有用，因为它把描述“做什么”的语句换成了“为什么这样做”。</p>
<p>条件语句的合并理由也同时指出了不要合并的理由：如果我认为这些检查的确彼此独立，的确不应该被视为同一次检查，我就不会使用本项重构。</p>
<h3 id="做法-1" tabindex="-1"><a class="header-anchor" href="#做法-1"><span>做法</span></a></h3>
<p>确定这些条件表达式都没有副作用。</p>
<p>如果某个条件表达式有副作用，可以先用将查询函数和修改函数分离（306）处理。</p>
<p>使用适当的逻辑运算符，将两个相关条件表达式合并为一个。</p>
<p>顺序执行的条件表达式用逻辑或来合并，嵌套的 if 语句用逻辑与来合并。</p>
<p>测试。</p>
<p>重复前面的合并过程，直到所有相关的条件表达式都合并到一起。</p>
<p>可以考虑对合并后的条件表达式实施提炼函数（106）。</p>
<h3 id="范例-1" tabindex="-1"><a class="header-anchor" href="#范例-1"><span>范例</span></a></h3>
<p>在走读代码的过程中，我看到了下面的代码片段：</p>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">function</span> <span class="token function">disabilityAmount</span><span class="token punctuation">(</span><span class="token parameter">anEmployee</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span>anEmployee<span class="token punctuation">.</span>seniority <span class="token operator">&lt;</span> <span class="token number">2</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span>anEmployee<span class="token punctuation">.</span>monthsDisabled <span class="token operator">></span> <span class="token number">12</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span>anEmployee<span class="token punctuation">.</span>isPartTime<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token comment">// compute the disability amount</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>这里有一连串的条件检查，都指向同样的结果。既然结果是相同的，就应该把这些条件检查合并成一条表达式。对于这样顺序执行的条件检查，可以用逻辑或运算符来合并。</p>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">function</span> <span class="token function">disabilityAmount</span><span class="token punctuation">(</span><span class="token parameter">anEmployee</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>anEmployee<span class="token punctuation">.</span>seniority <span class="token operator">&lt;</span> <span class="token number">2</span><span class="token punctuation">)</span></span>
<span class="line">   <span class="token operator">||</span> <span class="token punctuation">(</span>anEmployee<span class="token punctuation">.</span>monthsDisabled <span class="token operator">></span> <span class="token number">12</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span>anEmployee<span class="token punctuation">.</span>isPartTime<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token comment">// compute the disability amount</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>测试，然后把下一个条件检查也合并进来：</p>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">function</span> <span class="token function">disabilityAmount</span><span class="token punctuation">(</span><span class="token parameter">anEmployee</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>anEmployee<span class="token punctuation">.</span>seniority <span class="token operator">&lt;</span> <span class="token number">2</span><span class="token punctuation">)</span></span>
<span class="line">   <span class="token operator">||</span> <span class="token punctuation">(</span>anEmployee<span class="token punctuation">.</span>monthsDisabled <span class="token operator">></span> <span class="token number">12</span><span class="token punctuation">)</span></span>
<span class="line">   <span class="token operator">||</span> <span class="token punctuation">(</span>anEmployee<span class="token punctuation">.</span>isPartTime<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token comment">// compute the disability amount</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>合并完成后，再对这句条件表达式使用提炼函数（106）。</p>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">function</span> <span class="token function">disabilityAmount</span><span class="token punctuation">(</span><span class="token parameter">anEmployee</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">isNotEligableForDisability</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token comment">// compute the disability amount</span></span>
<span class="line"></span>
<span class="line"><span class="token keyword">function</span> <span class="token function">isNotEligableForDisability</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>anEmployee<span class="token punctuation">.</span>seniority <span class="token operator">&lt;</span> <span class="token number">2</span><span class="token punctuation">)</span></span>
<span class="line">     <span class="token operator">||</span> <span class="token punctuation">(</span>anEmployee<span class="token punctuation">.</span>monthsDisabled <span class="token operator">></span> <span class="token number">12</span><span class="token punctuation">)</span></span>
<span class="line">     <span class="token operator">||</span> <span class="token punctuation">(</span>anEmployee<span class="token punctuation">.</span>isPartTime<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3 id="范例-使用逻辑与" tabindex="-1"><a class="header-anchor" href="#范例-使用逻辑与"><span>范例：使用逻辑与</span></a></h3>
<p>上面的例子展示了用逻辑或合并条件表达式的做法。不过，我有可能遇到需要逻辑与的情况。例如，嵌套 if 语句的情况：</p>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">if</span> <span class="token punctuation">(</span>anEmployee<span class="token punctuation">.</span>onVacation<span class="token punctuation">)</span></span>
<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span>anEmployee<span class="token punctuation">.</span>seniority <span class="token operator">></span> <span class="token number">10</span><span class="token punctuation">)</span></span>
<span class="line">  <span class="token keyword">return</span> <span class="token number">1</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token keyword">return</span> <span class="token number">0.5</span><span class="token punctuation">;</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>可以用逻辑与运算符将其合并。</p>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>anEmployee<span class="token punctuation">.</span>onVacation<span class="token punctuation">)</span></span>
<span class="line">  <span class="token operator">&amp;</span>amp<span class="token punctuation">;</span><span class="token operator">&amp;</span>amp<span class="token punctuation">;</span> <span class="token punctuation">(</span>anEmployee<span class="token punctuation">.</span>seniority <span class="token operator">></span> <span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token number">1</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token keyword">return</span> <span class="token number">0.5</span><span class="token punctuation">;</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>如果原来的条件逻辑混杂了这两种情况，我也会根据需要组合使用逻辑与和逻辑或运算符。在这种时候，代码很可能变得混乱，所以我会频繁使用提炼函数（106），把代码变得可读。</p>
<h2 id="_10-3-以卫语句取代嵌套条件表达式-replace-nested-conditional-with-guard-clauses" tabindex="-1"><a class="header-anchor" href="#_10-3-以卫语句取代嵌套条件表达式-replace-nested-conditional-with-guard-clauses"><span>10.3 以卫语句取代嵌套条件表达式（Replace Nested Conditional with Guard Clauses）</span></a></h2>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">function</span> <span class="token function">getPayAmount</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token keyword">let</span> result<span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">if</span> <span class="token punctuation">(</span>isDead<span class="token punctuation">)</span> result <span class="token operator">=</span> <span class="token function">deadAmount</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">else</span> <span class="token punctuation">{</span></span>
<span class="line">    <span class="token keyword">if</span> <span class="token punctuation">(</span>isSeparated<span class="token punctuation">)</span> result <span class="token operator">=</span> <span class="token function">separatedAmount</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line">    <span class="token keyword">else</span> <span class="token punctuation">{</span></span>
<span class="line">      <span class="token keyword">if</span> <span class="token punctuation">(</span>isRetired<span class="token punctuation">)</span> result <span class="token operator">=</span> <span class="token function">retiredAmount</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line">      <span class="token keyword">else</span> result <span class="token operator">=</span> <span class="token function">normalPayAmount</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line">    <span class="token punctuation">}</span></span>
<span class="line">  <span class="token punctuation">}</span></span>
<span class="line">  <span class="token keyword">return</span> result<span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span>
<span class="line"><span class="token keyword">function</span> <span class="token function">getPayAmount</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token keyword">if</span> <span class="token punctuation">(</span>isDead<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token function">deadAmount</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">if</span> <span class="token punctuation">(</span>isSeparated<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token function">separatedAmount</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">if</span> <span class="token punctuation">(</span>isRetired<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token function">retiredAmount</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">return</span> <span class="token function">normalPayAmount</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3 id="动机-2" tabindex="-1"><a class="header-anchor" href="#动机-2"><span>动机</span></a></h3>
<p>根据我的经验，条件表达式通常有两种风格。第一种风格是：两个条件分支都属于正常行为。第二种风格则是：只有一个条件分支是正常行为，另一个分支则是异常的情况。</p>
<p>这两类条件表达式有不同的用途，这一点应该通过代码表现出来。如果两条分支都是正常行为，就应该使用形如 if...else...的条件表达式；如果某个条件极其罕见，就应该单独检查该条件，并在该条件为真时立刻从函数中返回。这样的单独检查常常被称为“卫语句”（guard clauses）。</p>
<p>以卫语句取代嵌套条件表达式的精髓就是：给某一条分支以特别的重视。如果使用 if-then-else 结构，你对 if 分支和 else 分支的重视是同等的。这样的代码结构传递给阅读者的消息就是：各个分支有同样的重要性。卫语句就不同了，它告诉阅读者：“这种情况不是本函数的核心逻辑所关心的，如果它真发生了，请做一些必要的整理工作，然后退出。”</p>
<p>“每个函数只能有一个入口和一个出口”的观念，根深蒂固于某些程序员的脑海里。我发现，当我处理他们编写的代码时，经常需要使用以卫语句取代嵌套条件表达式。现今的编程语言都会强制保证每个函数只有一个入口，至于“单一出口”规则，其实不是那么有用。在我看来，保持代码清晰才是最关键的：如果单一出口能使这个函数更清楚易读，那么就使用单一出口；否则就不必这么做。</p>
<h3 id="做法-2" tabindex="-1"><a class="header-anchor" href="#做法-2"><span>做法</span></a></h3>
<p>选中最外层需要被替换的条件逻辑，将其替换为卫语句。</p>
<p>测试。</p>
<p>有需要的话，重复上述步骤。</p>
<p>如果所有卫语句都引发同样的结果，可以使用合并条件表达式（263）合并之。</p>
<h3 id="范例-2" tabindex="-1"><a class="header-anchor" href="#范例-2"><span>范例</span></a></h3>
<p>下面的代码用于计算要支付给员工（employee）的工资。只有还在公司上班的员工才需要支付工资，所以这个函数需要检查两种“员工已经不在公司上班”的情况。</p>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">function</span> <span class="token function">payAmount</span><span class="token punctuation">(</span><span class="token parameter">employee</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">let</span> result<span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">if</span><span class="token punctuation">(</span>employee<span class="token punctuation">.</span>isSeparated<span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  result <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token literal-property property">amount</span><span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token literal-property property">reasonCode</span><span class="token operator">:</span><span class="token string">"SEP"</span><span class="token punctuation">}</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token punctuation">}</span></span>
<span class="line"> <span class="token keyword">else</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token keyword">if</span> <span class="token punctuation">(</span>employee<span class="token punctuation">.</span>isRetired<span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">   result <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token literal-property property">amount</span><span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token literal-property property">reasonCode</span><span class="token operator">:</span> <span class="token string">"RET"</span><span class="token punctuation">}</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token punctuation">}</span></span>
<span class="line">  <span class="token keyword">else</span> <span class="token punctuation">{</span></span>
<span class="line">   <span class="token comment">// logic to compute amount</span></span>
<span class="line">   lorem<span class="token punctuation">.</span><span class="token function">ipsum</span><span class="token punctuation">(</span>dolor<span class="token punctuation">.</span>sitAmet<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token number">1</span></span>
<span class="line">   <span class="token function">consectetur</span><span class="token punctuation">(</span>adipiscing<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">elit</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line">   sed<span class="token punctuation">.</span>do<span class="token punctuation">.</span>eiusmod <span class="token operator">=</span> tempor<span class="token punctuation">.</span>incididunt<span class="token punctuation">.</span><span class="token function">ut</span><span class="token punctuation">(</span>labore<span class="token punctuation">)</span> <span class="token operator">&amp;</span>amp<span class="token punctuation">;</span><span class="token operator">&amp;</span>amp<span class="token punctuation">;</span> <span class="token function">dolore</span><span class="token punctuation">(</span>magna<span class="token punctuation">.</span>aliqua<span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line">   ut<span class="token punctuation">.</span>enim<span class="token punctuation">.</span><span class="token function">ad</span><span class="token punctuation">(</span>minim<span class="token punctuation">.</span>veniam<span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line">   result <span class="token operator">=</span> <span class="token function">someFinalComputation</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token punctuation">}</span></span>
<span class="line"> <span class="token punctuation">}</span></span>
<span class="line"> <span class="token keyword">return</span> result<span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>嵌套的条件逻辑让我们看不清代码真实的含义。只有当前两个条件表达式都不为真的时候，这段代码才真正开始它的主要工作。所以，卫语句能让代码更清晰地阐述自己的意图。</p>
<p>一如既往地，我喜欢小步前进，所以我先处理最顶上的条件逻辑。</p>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">function</span> <span class="token function">payAmount</span><span class="token punctuation">(</span><span class="token parameter">employee</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">let</span> result<span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span>employee<span class="token punctuation">.</span>isSeparated<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token punctuation">{</span><span class="token literal-property property">amount</span><span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token literal-property property">reasonCode</span><span class="token operator">:</span> <span class="token string">"SEP"</span><span class="token punctuation">}</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span>employee<span class="token punctuation">.</span>isRetired<span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  result <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token literal-property property">amount</span><span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token literal-property property">reasonCode</span><span class="token operator">:</span> <span class="token string">"RET"</span><span class="token punctuation">}</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token punctuation">}</span></span>
<span class="line"> <span class="token keyword">else</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token comment">// logic to compute amount</span></span>
<span class="line">  lorem<span class="token punctuation">.</span><span class="token function">ipsum</span><span class="token punctuation">(</span>dolor<span class="token punctuation">.</span>sitAmet<span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token function">consectetur</span><span class="token punctuation">(</span>adipiscing<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">elit</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line">  sed<span class="token punctuation">.</span>do<span class="token punctuation">.</span>eiusmod <span class="token operator">=</span> tempor<span class="token punctuation">.</span>incididunt<span class="token punctuation">.</span><span class="token function">ut</span><span class="token punctuation">(</span>labore<span class="token punctuation">)</span> <span class="token operator">&amp;</span>amp<span class="token punctuation">;</span><span class="token operator">&amp;</span>amp<span class="token punctuation">;</span> <span class="token function">dolore</span><span class="token punctuation">(</span>magna<span class="token punctuation">.</span>aliqua<span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line">  ut<span class="token punctuation">.</span>enim<span class="token punctuation">.</span><span class="token function">ad</span><span class="token punctuation">(</span>minim<span class="token punctuation">.</span>veniam<span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line">  result <span class="token operator">=</span> <span class="token function">someFinalComputation</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token punctuation">}</span></span>
<span class="line"> <span class="token keyword">return</span> result<span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>做完这步修改，我执行测试，然后继续下一步。</p>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">function</span> <span class="token function">payAmount</span><span class="token punctuation">(</span><span class="token parameter">employee</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">let</span> result<span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span>employee<span class="token punctuation">.</span>isSeparated<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token punctuation">{</span><span class="token literal-property property">amount</span><span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token literal-property property">reasonCode</span><span class="token operator">:</span> <span class="token string">"SEP"</span><span class="token punctuation">}</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span>employee<span class="token punctuation">.</span>isRetired<span class="token punctuation">)</span>   <span class="token keyword">return</span> <span class="token punctuation">{</span><span class="token literal-property property">amount</span><span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token literal-property property">reasonCode</span><span class="token operator">:</span> <span class="token string">"RET"</span><span class="token punctuation">}</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token comment">// logic to compute amount</span></span>
<span class="line"> lorem<span class="token punctuation">.</span><span class="token function">ipsum</span><span class="token punctuation">(</span>dolor<span class="token punctuation">.</span>sitAmet<span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token function">consectetur</span><span class="token punctuation">(</span>adipiscing<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">elit</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"> sed<span class="token punctuation">.</span>do<span class="token punctuation">.</span>eiusmod <span class="token operator">=</span> tempor<span class="token punctuation">.</span>incididunt<span class="token punctuation">.</span><span class="token function">ut</span><span class="token punctuation">(</span>labore<span class="token punctuation">)</span> <span class="token operator">&amp;</span>amp<span class="token punctuation">;</span><span class="token operator">&amp;</span>amp<span class="token punctuation">;</span> <span class="token function">dolore</span><span class="token punctuation">(</span>magna<span class="token punctuation">.</span>aliqua<span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"> ut<span class="token punctuation">.</span>enim<span class="token punctuation">.</span><span class="token function">ad</span><span class="token punctuation">(</span>minim<span class="token punctuation">.</span>veniam<span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"> result <span class="token operator">=</span> <span class="token function">someFinalComputation</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">return</span> result<span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>此时，result 变量已经没有用处了，所以我把它删掉：</p>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">function</span> <span class="token function">payAmount</span><span class="token punctuation">(</span><span class="token parameter">employee</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">let</span> result<span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span>employee<span class="token punctuation">.</span>isSeparated<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token punctuation">{</span><span class="token literal-property property">amount</span><span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token literal-property property">reasonCode</span><span class="token operator">:</span> <span class="token string">"SEP"</span><span class="token punctuation">}</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span>employee<span class="token punctuation">.</span>isRetired<span class="token punctuation">)</span>   <span class="token keyword">return</span> <span class="token punctuation">{</span><span class="token literal-property property">amount</span><span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token literal-property property">reasonCode</span><span class="token operator">:</span> <span class="token string">"RET"</span><span class="token punctuation">}</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token comment">// logic to compute amount</span></span>
<span class="line"> lorem<span class="token punctuation">.</span><span class="token function">ipsum</span><span class="token punctuation">(</span>dolor<span class="token punctuation">.</span>sitAmet<span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token function">consectetur</span><span class="token punctuation">(</span>adipiscing<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">elit</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"> sed<span class="token punctuation">.</span>do<span class="token punctuation">.</span>eiusmod <span class="token operator">=</span> tempor<span class="token punctuation">.</span>incididunt<span class="token punctuation">.</span><span class="token function">ut</span><span class="token punctuation">(</span>labore<span class="token punctuation">)</span> <span class="token operator">&amp;</span>amp<span class="token punctuation">;</span><span class="token operator">&amp;</span>amp<span class="token punctuation">;</span> <span class="token function">dolore</span><span class="token punctuation">(</span>magna<span class="token punctuation">.</span>aliqua<span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"> ut<span class="token punctuation">.</span>enim<span class="token punctuation">.</span><span class="token function">ad</span><span class="token punctuation">(</span>minim<span class="token punctuation">.</span>veniam<span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">return</span> <span class="token function">someFinalComputation</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>能减少一个可变变量总是好的。</p>
<h3 id="范例-将条件反转" tabindex="-1"><a class="header-anchor" href="#范例-将条件反转"><span>范例：将条件反转</span></a></h3>
<p>审阅本书第 1 版的初稿时，Joshua Kerievsky 指出：我们常常可以将条件表达式反转，从而实现以卫语句取代嵌套条件表达式。为了拯救我可怜的想象力，他还好心帮我想了一个例子：</p>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">function</span> <span class="token function">adjustedCapital</span><span class="token punctuation">(</span><span class="token parameter">anInstrument</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">let</span> result <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span>anInstrument<span class="token punctuation">.</span>capital <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token keyword">if</span> <span class="token punctuation">(</span>anInstrument<span class="token punctuation">.</span>interestRate <span class="token operator">></span> <span class="token number">0</span> <span class="token operator">&amp;</span>amp<span class="token punctuation">;</span><span class="token operator">&amp;</span>amp<span class="token punctuation">;</span> anInstrument<span class="token punctuation">.</span>duration <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">   result <span class="token operator">=</span> <span class="token punctuation">(</span>anInstrument<span class="token punctuation">.</span>income <span class="token operator">/</span> anInstrument<span class="token punctuation">.</span>duration<span class="token punctuation">)</span> <span class="token operator">*</span> anInstrument<span class="token punctuation">.</span>adjustmentFactor<span class="token punctuation">;</span></span>
<span class="line">  <span class="token punctuation">}</span></span>
<span class="line"> <span class="token punctuation">}</span></span>
<span class="line"> <span class="token keyword">return</span> result<span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>同样地，我逐一进行替换。不过这次在插入卫语句时，我需要将相应的条件反转过来：</p>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">function</span> <span class="token function">adjustedCapital</span><span class="token punctuation">(</span><span class="token parameter">anInstrument</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">let</span> result <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span>anInstrument<span class="token punctuation">.</span>capital <span class="token operator">&lt;=</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">return</span> result<span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span>anInstrument<span class="token punctuation">.</span>interestRate <span class="token operator">></span> <span class="token number">0</span> <span class="token operator">&amp;</span>amp<span class="token punctuation">;</span><span class="token operator">&amp;</span>amp<span class="token punctuation">;</span> anInstrument<span class="token punctuation">.</span>duration <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  result <span class="token operator">=</span> <span class="token punctuation">(</span>anInstrument<span class="token punctuation">.</span>income <span class="token operator">/</span> anInstrument<span class="token punctuation">.</span>duration<span class="token punctuation">)</span> <span class="token operator">*</span> anInstrument<span class="token punctuation">.</span>adjustmentFactor<span class="token punctuation">;</span></span>
<span class="line"> <span class="token punctuation">}</span></span>
<span class="line"> <span class="token keyword">return</span> result<span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>下一个条件稍微复杂一点，所以我分两步进行反转。首先加入一个逻辑非操作：</p>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">function</span> <span class="token function">adjustedCapital</span><span class="token punctuation">(</span><span class="token parameter">anInstrument</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">let</span> result <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span>anInstrument<span class="token punctuation">.</span>capital <span class="token operator">&lt;=</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">return</span> result<span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token punctuation">(</span>anInstrument<span class="token punctuation">.</span>interestRate <span class="token operator">></span> <span class="token number">0</span> <span class="token operator">&amp;</span>amp<span class="token punctuation">;</span><span class="token operator">&amp;</span>amp<span class="token punctuation">;</span> anInstrument<span class="token punctuation">.</span>duration <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">return</span> result<span class="token punctuation">;</span></span>
<span class="line"> result <span class="token operator">=</span> <span class="token punctuation">(</span>anInstrument<span class="token punctuation">.</span>income <span class="token operator">/</span> anInstrument<span class="token punctuation">.</span>duration<span class="token punctuation">)</span> <span class="token operator">*</span> anInstrument<span class="token punctuation">.</span>adjustmentFactor<span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">return</span> result<span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>但是在这样的条件表达式中留下一个逻辑非，会把我的脑袋拧成一团乱麻，所以我把它简化成下面这样：</p>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line">  <span class="token keyword">function</span> <span class="token function">adjustedCapital</span><span class="token punctuation">(</span><span class="token parameter">anInstrument</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">let</span> result <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span>anInstrument<span class="token punctuation">.</span>capital <span class="token operator">&lt;=</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">return</span> result<span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span>anInstrument<span class="token punctuation">.</span>interestRate <span class="token operator">&lt;=</span> <span class="token number">0</span> <span class="token operator">||</span> anInstrument<span class="token punctuation">.</span>duration <span class="token operator">&lt;=</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">return</span> result<span class="token punctuation">;</span></span>
<span class="line"> result <span class="token operator">=</span> <span class="token punctuation">(</span>anInstrument<span class="token punctuation">.</span>income <span class="token operator">/</span> anInstrument<span class="token punctuation">.</span>duration<span class="token punctuation">)</span> <span class="token operator">*</span> anInstrument<span class="token punctuation">.</span>adjustmentFactor<span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">return</span> result<span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>这两行逻辑语句引发的结果一样，所以我可以用合并条件表达式（263）将其合并。</p>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">function</span> <span class="token function">adjustedCapital</span><span class="token punctuation">(</span><span class="token parameter">anInstrument</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">let</span> result <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span>   anInstrument<span class="token punctuation">.</span>capital      <span class="token operator">&lt;=</span> <span class="token number">0</span></span>
<span class="line">   <span class="token operator">||</span> anInstrument<span class="token punctuation">.</span>interestRate <span class="token operator">&lt;=</span> <span class="token number">0</span></span>
<span class="line">   <span class="token operator">||</span> anInstrument<span class="token punctuation">.</span>duration     <span class="token operator">&lt;=</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">return</span> result<span class="token punctuation">;</span></span>
<span class="line"> result <span class="token operator">=</span> <span class="token punctuation">(</span>anInstrument<span class="token punctuation">.</span>income <span class="token operator">/</span> anInstrument<span class="token punctuation">.</span>duration<span class="token punctuation">)</span> <span class="token operator">*</span> anInstrument<span class="token punctuation">.</span>adjustmentFactor<span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">return</span> result<span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>此时 result 变量做了两件事：一开始我把它设为 0，代表卫语句被触发时的返回值；然后又用最终计算的结果给它赋值。我可以彻底移除这个变量，避免用一个变量承担两重责任，而且又减少了一个可变变量。</p>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">function</span> <span class="token function">adjustedCapital</span><span class="token punctuation">(</span><span class="token parameter">anInstrument</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span>   anInstrument<span class="token punctuation">.</span>capital     <span class="token operator">&lt;=</span> <span class="token number">0</span></span>
<span class="line">   <span class="token operator">||</span> anInstrument<span class="token punctuation">.</span>interestRate <span class="token operator">&lt;=</span> <span class="token number">0</span></span>
<span class="line">   <span class="token operator">||</span> anInstrument<span class="token punctuation">.</span>duration   <span class="token operator">&lt;=</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">return</span> <span class="token punctuation">(</span>anInstrument<span class="token punctuation">.</span>income <span class="token operator">/</span> anInstrument<span class="token punctuation">.</span>duration<span class="token punctuation">)</span> <span class="token operator">*</span> anInstrument<span class="token punctuation">.</span>adjustmentFactor<span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>1 “lorem.ipsum……”是一篇常见于排版设计领域的文章，其内容为不具可读性的字符组合，目的是使阅读者只专注于观察段落的字型和版型。——译者注</p>
<h2 id="_10-4-以多态取代条件表达式-replace-conditional-with-polymorphism" tabindex="-1"><a class="header-anchor" href="#_10-4-以多态取代条件表达式-replace-conditional-with-polymorphism"><span>10.4 以多态取代条件表达式（Replace Conditional with Polymorphism）</span></a></h2>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">switch</span> <span class="token punctuation">(</span>bird<span class="token punctuation">.</span>type<span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">case</span> <span class="token string">'EuropeanSwallow'</span><span class="token operator">:</span></span>
<span class="line">  <span class="token keyword">return</span> <span class="token string">"average"</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">case</span> <span class="token string">'AfricanSwallow'</span><span class="token operator">:</span></span>
<span class="line">  <span class="token keyword">return</span> <span class="token punctuation">(</span>bird<span class="token punctuation">.</span>numberOfCoconuts <span class="token operator">></span> <span class="token number">2</span><span class="token punctuation">)</span> <span class="token operator">?</span> <span class="token string">"tired"</span> <span class="token operator">:</span> <span class="token string">"average"</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">case</span> <span class="token string">'NorwegianBlueParrot'</span><span class="token operator">:</span></span>
<span class="line">  <span class="token keyword">return</span> <span class="token punctuation">(</span>bird<span class="token punctuation">.</span>voltage <span class="token operator">></span> <span class="token number">100</span><span class="token punctuation">)</span> <span class="token operator">?</span> <span class="token string">"scorched"</span> <span class="token operator">:</span> <span class="token string">"beautiful"</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">default</span><span class="token operator">:</span></span>
<span class="line">  <span class="token keyword">return</span> <span class="token string">"unknown"</span><span class="token punctuation">;</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span class="token keyword">class</span> <span class="token class-name">EuropeanSwallow</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">get</span> <span class="token function">plumage</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token keyword">return</span> <span class="token string">"average"</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token punctuation">}</span></span>
<span class="line"><span class="token keyword">class</span> <span class="token class-name">AfricanSwallow</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">get</span> <span class="token function">plumage</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">   <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>numberOfCoconuts <span class="token operator">></span> <span class="token number">2</span><span class="token punctuation">)</span> <span class="token operator">?</span> <span class="token string">"tired"</span> <span class="token operator">:</span> <span class="token string">"average"</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token punctuation">}</span></span>
<span class="line"><span class="token keyword">class</span> <span class="token class-name">NorwegianBlueParrot</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">get</span> <span class="token function">plumage</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">   <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>voltage <span class="token operator">></span> <span class="token number">100</span><span class="token punctuation">)</span> <span class="token operator">?</span> <span class="token string">"scorched"</span> <span class="token operator">:</span> <span class="token string">"beautiful"</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3 id="动机-3" tabindex="-1"><a class="header-anchor" href="#动机-3"><span>动机</span></a></h3>
<p>复杂的条件逻辑是编程中最难理解的东西之一，因此我一直在寻求给条件逻辑添加结构。很多时候，我发现可以将条件逻辑拆分到不同的场景（或者叫高阶用例），从而拆解复杂的条件逻辑。这种拆分有时用条件逻辑本身的结构就足以表达，但使用类和多态能把逻辑的拆分表述得更清晰。</p>
<p>一个常见的场景是：我可以构造一组类型，每个类型处理各自的一种条件逻辑。例如，我会注意到，图书、音乐、食品的处理方式不同，这是因为它们分属不同类型的商品。最明显的征兆就是有好几个函数都有基于类型代码的 switch 语句。若果真如此，我就可以针对 switch 语句中的每种分支逻辑创建一个类，用多态来承载各个类型特有的行为，从而去除重复的分支逻辑。</p>
<p>另一种情况是：有一个基础逻辑，在其上又有一些变体。基础逻辑可能是最常用的，也可能是最简单的。我可以把基础逻辑放进超类，这样我可以首先理解这部分逻辑，暂时不管各种变体，然后我可以把每种变体逻辑单独放进一个子类，其中的代码着重强调与基础逻辑的差异。</p>
<p>多态是面向对象编程的关键特性之一。跟其他一切有用的特性一样，它也很容易被滥用。我曾经遇到有人争论说所有条件逻辑都应该用多态取代。我不赞同这种观点。我的大部分条件逻辑只用到了基本的条件语句——if/else 和 switch/case，并不需要劳师动众地引入多态。但如果发现如前所述的复杂条件逻辑，多态是改善这种情况的有力工具。</p>
<h3 id="做法-3" tabindex="-1"><a class="header-anchor" href="#做法-3"><span>做法</span></a></h3>
<p>如果现有的类尚不具备多态行为，就用工厂函数创建之，令工厂函数返回恰当的对象实例。</p>
<p>在调用方代码中使用工厂函数获得对象实例。</p>
<p>将带有条件逻辑的函数移到超类中。</p>
<p>如果条件逻辑还未提炼至独立的函数，首先对其使用提炼函数（106）。</p>
<p>任选一个子类，在其中建立一个函数，使之覆写超类中容纳条件表达式的那个函数。将与该子类相关的条件表达式分支复制到新函数中，并对它进行适当调整。</p>
<p>重复上述过程，处理其他条件分支。</p>
<p>在超类函数中保留默认情况的逻辑。或者，如果超类应该是抽象的，就把该函数声明为 abstract，或在其中直接抛出异常，表明计算责任都在子类中。</p>
<h3 id="范例-3" tabindex="-1"><a class="header-anchor" href="#范例-3"><span>范例</span></a></h3>
<p>我的朋友有一群鸟儿，他想知道这些鸟飞得有多快，以及它们的羽毛是什么样的。所以我们写了一小段程序来判断这些信息。</p>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">function</span> <span class="token function">plumages</span><span class="token punctuation">(</span><span class="token parameter">birds</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">Map</span><span class="token punctuation">(</span>birds<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token parameter">b</span> <span class="token operator">=></span> <span class="token punctuation">[</span>b<span class="token punctuation">.</span>name<span class="token punctuation">,</span> <span class="token function">plumage</span><span class="token punctuation">(</span>b<span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"><span class="token keyword">function</span> <span class="token function">speeds</span><span class="token punctuation">(</span><span class="token parameter">birds</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">Map</span><span class="token punctuation">(</span>birds<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token parameter">b</span> <span class="token operator">=></span> <span class="token punctuation">[</span>b<span class="token punctuation">.</span>name<span class="token punctuation">,</span> <span class="token function">airSpeedVelocity</span><span class="token punctuation">(</span>b<span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span>
<span class="line"><span class="token keyword">function</span> <span class="token function">plumage</span><span class="token punctuation">(</span><span class="token parameter">bird</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">switch</span> <span class="token punctuation">(</span>bird<span class="token punctuation">.</span>type<span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">case</span> <span class="token string">'EuropeanSwallow'</span><span class="token operator">:</span></span>
<span class="line">  <span class="token keyword">return</span> <span class="token string">"average"</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">case</span> <span class="token string">'AfricanSwallow'</span><span class="token operator">:</span></span>
<span class="line">  <span class="token keyword">return</span> <span class="token punctuation">(</span>bird<span class="token punctuation">.</span>numberOfCoconuts <span class="token operator">></span> <span class="token number">2</span><span class="token punctuation">)</span> <span class="token operator">?</span> <span class="token string">"tired"</span> <span class="token operator">:</span> <span class="token string">"average"</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">case</span> <span class="token string">'NorwegianBlueParrot'</span><span class="token operator">:</span></span>
<span class="line">  <span class="token keyword">return</span> <span class="token punctuation">(</span>bird<span class="token punctuation">.</span>voltage <span class="token operator">></span> <span class="token number">100</span><span class="token punctuation">)</span> <span class="token operator">?</span> <span class="token string">"scorched"</span> <span class="token operator">:</span> <span class="token string">"beautiful"</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">default</span><span class="token operator">:</span></span>
<span class="line">  <span class="token keyword">return</span> <span class="token string">"unknown"</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token punctuation">}</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span>
<span class="line"><span class="token keyword">function</span> <span class="token function">airSpeedVelocity</span><span class="token punctuation">(</span><span class="token parameter">bird</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">switch</span> <span class="token punctuation">(</span>bird<span class="token punctuation">.</span>type<span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">case</span> <span class="token string">'EuropeanSwallow'</span><span class="token operator">:</span></span>
<span class="line">  <span class="token keyword">return</span> <span class="token number">35</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">case</span> <span class="token string">'AfricanSwallow'</span><span class="token operator">:</span></span>
<span class="line">  <span class="token keyword">return</span> <span class="token number">40</span> <span class="token operator">-</span> <span class="token number">2</span> <span class="token operator">*</span> bird<span class="token punctuation">.</span>numberOfCoconuts<span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">case</span> <span class="token string">'NorwegianBlueParrot'</span><span class="token operator">:</span></span>
<span class="line">  <span class="token keyword">return</span> <span class="token punctuation">(</span>bird<span class="token punctuation">.</span>isNailed<span class="token punctuation">)</span> <span class="token operator">?</span> <span class="token number">0</span> <span class="token operator">:</span> <span class="token number">10</span> <span class="token operator">+</span> bird<span class="token punctuation">.</span>voltage <span class="token operator">/</span> <span class="token number">10</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">default</span><span class="token operator">:</span></span>
<span class="line">  <span class="token keyword">return</span> <span class="token keyword">null</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token punctuation">}</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>有两个不同的操作，其行为都随着“鸟的类型”发生变化，因此可以创建出对应的类，用多态来处理各类型特有的行为。</p>
<p>我先对 airSpeedVelocity 和 plumage 两个函数使用函数组合成类（144）。</p>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">function</span> <span class="token function">plumage</span><span class="token punctuation">(</span><span class="token parameter">bird</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">Bird</span><span class="token punctuation">(</span>bird<span class="token punctuation">)</span><span class="token punctuation">.</span>plumage<span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span>
<span class="line"><span class="token keyword">function</span> <span class="token function">airSpeedVelocity</span><span class="token punctuation">(</span><span class="token parameter">bird</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">Bird</span><span class="token punctuation">(</span>bird<span class="token punctuation">)</span><span class="token punctuation">.</span>airSpeedVelocity<span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"><span class="token keyword">class</span> <span class="token class-name">Bird</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token function">constructor</span><span class="token punctuation">(</span><span class="token parameter">birdObject</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  Object<span class="token punctuation">.</span><span class="token function">assign</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> birdObject<span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token punctuation">}</span></span>
<span class="line"> <span class="token keyword">get</span> <span class="token function">plumage</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token keyword">switch</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>type<span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token keyword">case</span> <span class="token string">'EuropeanSwallow'</span><span class="token operator">:</span></span>
<span class="line">   <span class="token keyword">return</span> <span class="token string">"average"</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">case</span> <span class="token string">'AfricanSwallow'</span><span class="token operator">:</span></span>
<span class="line">   <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>numberOfCoconuts <span class="token operator">></span> <span class="token number">2</span><span class="token punctuation">)</span> <span class="token operator">?</span> <span class="token string">"tired"</span> <span class="token operator">:</span> <span class="token string">"average"</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">case</span> <span class="token string">'NorwegianBlueParrot'</span><span class="token operator">:</span></span>
<span class="line">   <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>voltage <span class="token operator">></span> <span class="token number">100</span><span class="token punctuation">)</span> <span class="token operator">?</span> <span class="token string">"scorched"</span> <span class="token operator">:</span> <span class="token string">"beautiful"</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">default</span><span class="token operator">:</span></span>
<span class="line">   <span class="token keyword">return</span> <span class="token string">"unknown"</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token punctuation">}</span></span>
<span class="line"> <span class="token punctuation">}</span></span>
<span class="line"> <span class="token keyword">get</span> <span class="token function">airSpeedVelocity</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token keyword">switch</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>type<span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token keyword">case</span> <span class="token string">'EuropeanSwallow'</span><span class="token operator">:</span></span>
<span class="line">   <span class="token keyword">return</span> <span class="token number">35</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">case</span> <span class="token string">'AfricanSwallow'</span><span class="token operator">:</span></span>
<span class="line">   <span class="token keyword">return</span> <span class="token number">40</span> <span class="token operator">-</span> <span class="token number">2</span> <span class="token operator">*</span> <span class="token keyword">this</span><span class="token punctuation">.</span>numberOfCoconuts<span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">case</span> <span class="token string">'NorwegianBlueParrot'</span><span class="token operator">:</span></span>
<span class="line">   <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>isNailed<span class="token punctuation">)</span> <span class="token operator">?</span> <span class="token number">0</span> <span class="token operator">:</span> <span class="token number">10</span> <span class="token operator">+</span> <span class="token keyword">this</span><span class="token punctuation">.</span>voltage <span class="token operator">/</span> <span class="token number">10</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">default</span><span class="token operator">:</span></span>
<span class="line">   <span class="token keyword">return</span> <span class="token keyword">null</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token punctuation">}</span></span>
<span class="line"> <span class="token punctuation">}</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>然后针对每种鸟创建一个子类，用一个工厂函数来实例化合适的子类对象。</p>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">function</span> <span class="token function">plumage</span><span class="token punctuation">(</span><span class="token parameter">bird</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token keyword">return</span> <span class="token function">createBird</span><span class="token punctuation">(</span>bird<span class="token punctuation">)</span><span class="token punctuation">.</span>plumage<span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span>
<span class="line"><span class="token keyword">function</span> <span class="token function">airSpeedVelocity</span><span class="token punctuation">(</span><span class="token parameter">bird</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token keyword">return</span> <span class="token function">createBird</span><span class="token punctuation">(</span>bird<span class="token punctuation">)</span><span class="token punctuation">.</span>airSpeedVelocity<span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span>
<span class="line"><span class="token keyword">function</span> <span class="token function">createBird</span><span class="token punctuation">(</span><span class="token parameter">bird</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token keyword">switch</span> <span class="token punctuation">(</span>bird<span class="token punctuation">.</span>type<span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">    <span class="token keyword">case</span> <span class="token string">"EuropeanSwallow"</span><span class="token operator">:</span></span>
<span class="line">      <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">EuropeanSwallow</span><span class="token punctuation">(</span>bird<span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line">    <span class="token keyword">case</span> <span class="token string">"AfricanSwallow"</span><span class="token operator">:</span></span>
<span class="line">      <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">AfricanSwallow</span><span class="token punctuation">(</span>bird<span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line">    <span class="token keyword">case</span> <span class="token string">"NorweigianBlueParrot"</span><span class="token operator">:</span></span>
<span class="line">      <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">NorwegianBlueParrot</span><span class="token punctuation">(</span>bird<span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line">    <span class="token keyword">default</span><span class="token operator">:</span></span>
<span class="line">      <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">Bird</span><span class="token punctuation">(</span>bird<span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token punctuation">}</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"><span class="token keyword">class</span> <span class="token class-name">EuropeanSwallow</span> <span class="token keyword">extends</span> <span class="token class-name">Bird</span> <span class="token punctuation">{</span><span class="token punctuation">}</span></span>
<span class="line"></span>
<span class="line"><span class="token keyword">class</span> <span class="token class-name">AfricanSwallow</span> <span class="token keyword">extends</span> <span class="token class-name">Bird</span> <span class="token punctuation">{</span><span class="token punctuation">}</span></span>
<span class="line"></span>
<span class="line"><span class="token keyword">class</span> <span class="token class-name">NorwegianBlueParrot</span> <span class="token keyword">extends</span> <span class="token class-name">Bird</span> <span class="token punctuation">{</span><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>现在我已经有了需要的类结构，可以处理两个条件逻辑了。先从 plumage 函数开始，我从 switch 语句中选一个分支，在适当的子类中覆写这个逻辑。</p>
<h4 id="class-europeanswallow" tabindex="-1"><a class="header-anchor" href="#class-europeanswallow"><span>class EuropeanSwallow...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">get</span> <span class="token function">plumage</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token keyword">return</span> <span class="token string">"average"</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h4 id="class-bird" tabindex="-1"><a class="header-anchor" href="#class-bird"><span>class Bird...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">get</span> <span class="token function">plumage</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">switch</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>type<span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">case</span> <span class="token string">'EuropeanSwallow'</span><span class="token operator">:</span></span>
<span class="line">  <span class="token keyword">throw</span> <span class="token string">"oops"</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">case</span> <span class="token string">'AfricanSwallow'</span><span class="token operator">:</span></span>
<span class="line">  <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>numberOfCoconuts <span class="token operator">></span> <span class="token number">2</span><span class="token punctuation">)</span> <span class="token operator">?</span> <span class="token string">"tired"</span> <span class="token operator">:</span> <span class="token string">"average"</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">case</span> <span class="token string">'NorwegianBlueParrot'</span><span class="token operator">:</span></span>
<span class="line">  <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>voltage <span class="token operator">></span> <span class="token number">100</span><span class="token punctuation">)</span> <span class="token operator">?</span> <span class="token string">"scorched"</span> <span class="token operator">:</span> <span class="token string">"beautiful"</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">default</span><span class="token operator">:</span></span>
<span class="line">  <span class="token keyword">return</span> <span class="token string">"unknown"</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token punctuation">}</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>在超类中，我把对应的逻辑分支改为抛出异常，因为我总是偏执地担心出错。</p>
<p>此时我就可以编译并测试。如果一切顺利的话，我可以接着处理下一个分支。</p>
<h4 id="class-africanswallow" tabindex="-1"><a class="header-anchor" href="#class-africanswallow"><span>class AfricanSwallow...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">get</span> <span class="token function">plumage</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">   <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>numberOfCoconuts <span class="token operator">></span> <span class="token number">2</span><span class="token punctuation">)</span> <span class="token operator">?</span> <span class="token string">"tired"</span> <span class="token operator">:</span> <span class="token string">"average"</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>然后是挪威蓝鹦鹉（Norwegian Blue）的分支。</p>
<h4 id="class-norwegianblueparrot" tabindex="-1"><a class="header-anchor" href="#class-norwegianblueparrot"><span>class NorwegianBlueParrot...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">get</span> <span class="token function">plumage</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">   <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>voltage <span class="token operator">></span><span class="token number">100</span><span class="token punctuation">)</span> <span class="token operator">?</span> <span class="token string">"scorched"</span> <span class="token operator">:</span> <span class="token string">"beautiful"</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>超类函数保留下来处理默认情况。</p>
<h4 id="class-bird-1" tabindex="-1"><a class="header-anchor" href="#class-bird-1"><span>class Bird...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">get</span> <span class="token function">plumage</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token keyword">return</span> <span class="token string">"unknown"</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>airSpeedVelocity 也如法炮制。完成以后，代码大致如下（我还对顶层的 airSpeedVelocity 和 plumage 函数做了内联处理）：</p>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">function</span> <span class="token function">plumages</span><span class="token punctuation">(</span><span class="token parameter">birds</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">Map</span><span class="token punctuation">(</span>birds</span>
<span class="line">         <span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token parameter">b</span> <span class="token operator">=></span> <span class="token function">createBird</span><span class="token punctuation">(</span>b<span class="token punctuation">)</span><span class="token punctuation">)</span></span>
<span class="line">         <span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token parameter">bird</span> <span class="token operator">=></span> <span class="token punctuation">[</span>bird<span class="token punctuation">.</span>name<span class="token punctuation">,</span> bird<span class="token punctuation">.</span>plumage<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"><span class="token keyword">function</span> <span class="token function">speeds</span><span class="token punctuation">(</span><span class="token parameter">birds</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">Map</span><span class="token punctuation">(</span>birds</span>
<span class="line">         <span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token parameter">b</span> <span class="token operator">=></span> <span class="token function">createBird</span><span class="token punctuation">(</span>b<span class="token punctuation">)</span><span class="token punctuation">)</span></span>
<span class="line">         <span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token parameter">bird</span> <span class="token operator">=></span> <span class="token punctuation">[</span>bird<span class="token punctuation">.</span>name<span class="token punctuation">,</span> bird<span class="token punctuation">.</span>airSpeedVelocity<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span>
<span class="line"><span class="token keyword">function</span> <span class="token function">createBird</span><span class="token punctuation">(</span><span class="token parameter">bird</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">switch</span> <span class="token punctuation">(</span>bird<span class="token punctuation">.</span>type<span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">case</span> <span class="token string">'EuropeanSwallow'</span><span class="token operator">:</span></span>
<span class="line">  <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">EuropeanSwallow</span><span class="token punctuation">(</span>bird<span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">case</span> <span class="token string">'AfricanSwallow'</span><span class="token operator">:</span></span>
<span class="line">  <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">AfricanSwallow</span><span class="token punctuation">(</span>bird<span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">case</span> <span class="token string">'NorwegianBlueParrot'</span><span class="token operator">:</span></span>
<span class="line">  <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">NorwegianBlueParrot</span><span class="token punctuation">(</span>bird<span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">default</span><span class="token operator">:</span></span>
<span class="line">  <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">Bird</span><span class="token punctuation">(</span>bird<span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token punctuation">}</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span>
<span class="line"><span class="token keyword">class</span> <span class="token class-name">Bird</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token function">constructor</span><span class="token punctuation">(</span><span class="token parameter">birdObject</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  Object<span class="token punctuation">.</span><span class="token function">assign</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> birdObject<span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token punctuation">}</span></span>
<span class="line"> <span class="token keyword">get</span> <span class="token function">plumage</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token keyword">return</span> <span class="token string">"unknown"</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token punctuation">}</span></span>
<span class="line"> <span class="token keyword">get</span> <span class="token function">airSpeedVelocity</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token keyword">return</span> <span class="token keyword">null</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token punctuation">}</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"><span class="token keyword">class</span> <span class="token class-name">EuropeanSwallow</span> <span class="token keyword">extends</span> <span class="token class-name">Bird</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">get</span> <span class="token function">plumage</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token keyword">return</span> <span class="token string">"average"</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token punctuation">}</span></span>
<span class="line"> <span class="token keyword">get</span> <span class="token function">airSpeedVelocity</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token keyword">return</span> <span class="token number">35</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token punctuation">}</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"><span class="token keyword">class</span> <span class="token class-name">AfricanSwallow</span> <span class="token keyword">extends</span> <span class="token class-name">Bird</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">get</span> <span class="token function">plumage</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>numberOfCoconuts <span class="token operator">></span> <span class="token number">2</span><span class="token punctuation">)</span> <span class="token operator">?</span> <span class="token string">"tired"</span> <span class="token operator">:</span> <span class="token string">"average"</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token punctuation">}</span></span>
<span class="line"> <span class="token keyword">get</span> <span class="token function">airSpeedVelocity</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token keyword">return</span> <span class="token number">40</span> <span class="token operator">-</span> <span class="token number">2</span> <span class="token operator">*</span> <span class="token keyword">this</span><span class="token punctuation">.</span>numberOfCoconuts<span class="token punctuation">;</span></span>
<span class="line"> <span class="token punctuation">}</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"><span class="token keyword">class</span> <span class="token class-name">NorwegianBlueParrot</span> <span class="token keyword">extends</span> <span class="token class-name">Bird</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">get</span> <span class="token function">plumage</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>voltage <span class="token operator">></span> <span class="token number">100</span><span class="token punctuation">)</span> <span class="token operator">?</span> <span class="token string">"scorched"</span> <span class="token operator">:</span> <span class="token string">"beautiful"</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token punctuation">}</span></span>
<span class="line"> <span class="token keyword">get</span> <span class="token function">airSpeedVelocity</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>isNailed<span class="token punctuation">)</span> <span class="token operator">?</span> <span class="token number">0</span> <span class="token operator">:</span> <span class="token number">10</span> <span class="token operator">+</span> <span class="token keyword">this</span><span class="token punctuation">.</span>voltage <span class="token operator">/</span> <span class="token number">10</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token punctuation">}</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>看着最终的代码，可以看出 Bird 超类并不是必需的。在 JavaScript 中，多态不一定需要类型层级，只要对象实现了适当的函数就行。但在这个例子中，我愿意保留这个不必要的超类，因为它能帮助阐释各个子类与问题域之间的关系。</p>
<h3 id="范例-用多态处理变体逻辑" tabindex="-1"><a class="header-anchor" href="#范例-用多态处理变体逻辑"><span>范例：用多态处理变体逻辑</span></a></h3>
<p>在前面的例子中，“鸟”的类型体系是一个清晰的泛化体系：超类是抽象的“鸟”，子类是各种具体的鸟。这是教科书（包括我写的书）中经常讨论的继承和多态，但并不是实践中使用继承的唯一方式。实际上，这种方式很可能不是最常用或最好的方式。另一种使用继承的情况是：我想表达某个对象与另一个对象大体类似，但又有一些不同之处。</p>
<p>下面有一个这样的例子：有一家评级机构，要对远洋航船的航行进行投资评级。这家评级机构会给出“A”或者“B”两种评级，取决于多种风险和盈利潜力的因素。在评估风险时，既要考虑航程本身的特征，也要考虑船长过往航行的历史。</p>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">function</span> <span class="token function">rating</span><span class="token punctuation">(</span><span class="token parameter">voyage<span class="token punctuation">,</span> history</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">const</span> vpf <span class="token operator">=</span> <span class="token function">voyageProfitFactor</span><span class="token punctuation">(</span>voyage<span class="token punctuation">,</span> history<span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">const</span> vr <span class="token operator">=</span> <span class="token function">voyageRisk</span><span class="token punctuation">(</span>voyage<span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">const</span> chr <span class="token operator">=</span> <span class="token function">captainHistoryRisk</span><span class="token punctuation">(</span>voyage<span class="token punctuation">,</span> history<span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span>vpf <span class="token operator">*</span> <span class="token number">3</span> <span class="token operator">></span> <span class="token punctuation">(</span>vr <span class="token operator">+</span> chr <span class="token operator">*</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token string">"A"</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">else</span> <span class="token keyword">return</span> <span class="token string">"B"</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"><span class="token keyword">function</span> <span class="token function">voyageRisk</span><span class="token punctuation">(</span><span class="token parameter">voyage</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">let</span> result <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span>voyage<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">4</span><span class="token punctuation">)</span> result <span class="token operator">+=</span> <span class="token number">2</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span>voyage<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">8</span><span class="token punctuation">)</span> result <span class="token operator">+=</span> voyage<span class="token punctuation">.</span>length <span class="token operator">-</span> <span class="token number">8</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token string">"china"</span><span class="token punctuation">,</span> <span class="token string">"east-indies"</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">includes</span><span class="token punctuation">(</span>voyage<span class="token punctuation">.</span>zone<span class="token punctuation">)</span><span class="token punctuation">)</span> result <span class="token operator">+=</span> <span class="token number">4</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">return</span> Math<span class="token punctuation">.</span><span class="token function">max</span><span class="token punctuation">(</span>result<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"><span class="token keyword">function</span> <span class="token function">captainHistoryRisk</span><span class="token punctuation">(</span><span class="token parameter">voyage<span class="token punctuation">,</span> history</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">let</span> result <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span>history<span class="token punctuation">.</span>length <span class="token operator">&lt;</span> <span class="token number">5</span><span class="token punctuation">)</span> result <span class="token operator">+=</span> <span class="token number">4</span><span class="token punctuation">;</span></span>
<span class="line"> result <span class="token operator">+=</span> history<span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span><span class="token parameter">v</span> <span class="token operator">=></span> v<span class="token punctuation">.</span>profit <span class="token operator">&lt;</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">.</span>length<span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span>voyage<span class="token punctuation">.</span>zone <span class="token operator">===</span> <span class="token string">"china"</span> <span class="token operator">&amp;</span>amp<span class="token punctuation">;</span><span class="token operator">&amp;</span>amp<span class="token punctuation">;</span> <span class="token function">hasChina</span><span class="token punctuation">(</span>history<span class="token punctuation">)</span><span class="token punctuation">)</span> result <span class="token operator">-=</span> <span class="token number">2</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">return</span> Math<span class="token punctuation">.</span><span class="token function">max</span><span class="token punctuation">(</span>result<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"><span class="token keyword">function</span> <span class="token function">hasChina</span><span class="token punctuation">(</span><span class="token parameter">history</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">return</span> history<span class="token punctuation">.</span><span class="token function">some</span><span class="token punctuation">(</span><span class="token parameter">v</span> <span class="token operator">=></span> <span class="token string">"china"</span> <span class="token operator">===</span> v<span class="token punctuation">.</span>zone<span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"><span class="token keyword">function</span> <span class="token function">voyageProfitFactor</span><span class="token punctuation">(</span><span class="token parameter">voyage<span class="token punctuation">,</span> history</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">let</span> result <span class="token operator">=</span> <span class="token number">2</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span>voyage<span class="token punctuation">.</span>zone <span class="token operator">===</span> <span class="token string">"china"</span><span class="token punctuation">)</span> result <span class="token operator">+=</span> <span class="token number">1</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span>voyage<span class="token punctuation">.</span>zone <span class="token operator">===</span> <span class="token string">"east-indies"</span><span class="token punctuation">)</span> result <span class="token operator">+=</span> <span class="token number">1</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span>voyage<span class="token punctuation">.</span>zone <span class="token operator">===</span> <span class="token string">"china"</span> <span class="token operator">&amp;</span>amp<span class="token punctuation">;</span><span class="token operator">&amp;</span>amp<span class="token punctuation">;</span> <span class="token function">hasChina</span><span class="token punctuation">(</span>history<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  result <span class="token operator">+=</span> <span class="token number">3</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">if</span> <span class="token punctuation">(</span>history<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">10</span><span class="token punctuation">)</span> result <span class="token operator">+=</span> <span class="token number">1</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">if</span> <span class="token punctuation">(</span>voyage<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">12</span><span class="token punctuation">)</span> result <span class="token operator">+=</span> <span class="token number">1</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">if</span> <span class="token punctuation">(</span>voyage<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">18</span><span class="token punctuation">)</span> result <span class="token operator">-=</span> <span class="token number">1</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token punctuation">}</span></span>
<span class="line"> <span class="token keyword">else</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token keyword">if</span> <span class="token punctuation">(</span>history<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">8</span><span class="token punctuation">)</span> result <span class="token operator">+=</span> <span class="token number">1</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">if</span> <span class="token punctuation">(</span>voyage<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">14</span><span class="token punctuation">)</span> result <span class="token operator">-=</span> <span class="token number">1</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token punctuation">}</span></span>
<span class="line"> <span class="token keyword">return</span> result<span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>voyageRisk 和 captainHistoryRisk 两个函数负责打出风险分数，voyageProfitFactor 负责打出盈利潜力分数，rating 函数将 3 个分数组合到一起，给出一次航行的综合评级。</p>
<p>调用方的代码大概是这样：</p>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">const</span> voyage <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token literal-property property">zone</span><span class="token operator">:</span> <span class="token string">"west-indies"</span><span class="token punctuation">,</span> <span class="token literal-property property">length</span><span class="token operator">:</span> <span class="token number">10</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token keyword">const</span> history <span class="token operator">=</span> <span class="token punctuation">[</span></span>
<span class="line">  <span class="token punctuation">{</span> <span class="token literal-property property">zone</span><span class="token operator">:</span> <span class="token string">"east-indies"</span><span class="token punctuation">,</span> <span class="token literal-property property">profit</span><span class="token operator">:</span> <span class="token number">5</span> <span class="token punctuation">}</span><span class="token punctuation">,</span></span>
<span class="line">  <span class="token punctuation">{</span> <span class="token literal-property property">zone</span><span class="token operator">:</span> <span class="token string">"west-indies"</span><span class="token punctuation">,</span> <span class="token literal-property property">profit</span><span class="token operator">:</span> <span class="token number">15</span> <span class="token punctuation">}</span><span class="token punctuation">,</span></span>
<span class="line">  <span class="token punctuation">{</span> <span class="token literal-property property">zone</span><span class="token operator">:</span> <span class="token string">"china"</span><span class="token punctuation">,</span> <span class="token literal-property property">profit</span><span class="token operator">:</span> <span class="token operator">-</span><span class="token number">2</span> <span class="token punctuation">}</span><span class="token punctuation">,</span></span>
<span class="line">  <span class="token punctuation">{</span> <span class="token literal-property property">zone</span><span class="token operator">:</span> <span class="token string">"west-africa"</span><span class="token punctuation">,</span> <span class="token literal-property property">profit</span><span class="token operator">:</span> <span class="token number">7</span> <span class="token punctuation">}</span><span class="token punctuation">,</span></span>
<span class="line"><span class="token punctuation">]</span><span class="token punctuation">;</span></span>
<span class="line"></span>
<span class="line"><span class="token keyword">const</span> myRating <span class="token operator">=</span> <span class="token function">rating</span><span class="token punctuation">(</span>voyage<span class="token punctuation">,</span> history<span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>代码中有两处同样的条件逻辑，都在询问“是否有到中国的航程”以及“船长是否曾去过中国”。</p>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">function</span> <span class="token function">rating</span><span class="token punctuation">(</span><span class="token parameter">voyage<span class="token punctuation">,</span> history</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">const</span> vpf <span class="token operator">=</span> <span class="token function">voyageProfitFactor</span><span class="token punctuation">(</span>voyage<span class="token punctuation">,</span> history<span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">const</span> vr <span class="token operator">=</span> <span class="token function">voyageRisk</span><span class="token punctuation">(</span>voyage<span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">const</span> chr <span class="token operator">=</span> <span class="token function">captainHistoryRisk</span><span class="token punctuation">(</span>voyage<span class="token punctuation">,</span> history<span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span>vpf <span class="token operator">*</span> <span class="token number">3</span> <span class="token operator">></span> <span class="token punctuation">(</span>vr <span class="token operator">+</span> chr <span class="token operator">*</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token string">"A"</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">else</span> <span class="token keyword">return</span> <span class="token string">"B"</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"><span class="token keyword">function</span> <span class="token function">voyageRisk</span><span class="token punctuation">(</span><span class="token parameter">voyage</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">let</span> result <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span>voyage<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">4</span><span class="token punctuation">)</span> result <span class="token operator">+=</span> <span class="token number">2</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span>voyage<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">8</span><span class="token punctuation">)</span> result <span class="token operator">+=</span> voyage<span class="token punctuation">.</span>length <span class="token operator">-</span> <span class="token number">8</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token string">"china"</span><span class="token punctuation">,</span> <span class="token string">"east-indies"</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">includes</span><span class="token punctuation">(</span>voyage<span class="token punctuation">.</span>zone<span class="token punctuation">)</span><span class="token punctuation">)</span> result <span class="token operator">+=</span> <span class="token number">4</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">return</span> Math<span class="token punctuation">.</span><span class="token function">max</span><span class="token punctuation">(</span>result<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"><span class="token keyword">function</span> <span class="token function">captainHistoryRisk</span><span class="token punctuation">(</span><span class="token parameter">voyage<span class="token punctuation">,</span> history</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">let</span> result <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span>history<span class="token punctuation">.</span>length <span class="token operator">&lt;</span> <span class="token number">5</span><span class="token punctuation">)</span> result <span class="token operator">+=</span> <span class="token number">4</span><span class="token punctuation">;</span></span>
<span class="line"> result <span class="token operator">+=</span> history<span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span><span class="token parameter">v</span> <span class="token operator">=></span> v<span class="token punctuation">.</span>profit <span class="token operator">&lt;</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">.</span>length<span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span>voyage<span class="token punctuation">.</span>zone <span class="token operator">===</span> <span class="token string">"china"</span> <span class="token operator">&amp;</span>amp<span class="token punctuation">;</span><span class="token operator">&amp;</span>amp<span class="token punctuation">;</span> <span class="token function">hasChina</span><span class="token punctuation">(</span>history<span class="token punctuation">)</span><span class="token punctuation">)</span> result <span class="token operator">-=</span> <span class="token number">2</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">return</span> Math<span class="token punctuation">.</span><span class="token function">max</span><span class="token punctuation">(</span>result<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"><span class="token keyword">function</span> <span class="token function">hasChina</span><span class="token punctuation">(</span><span class="token parameter">history</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">return</span> history<span class="token punctuation">.</span><span class="token function">some</span><span class="token punctuation">(</span><span class="token parameter">v</span> <span class="token operator">=></span> <span class="token string">"china"</span> <span class="token operator">===</span> v<span class="token punctuation">.</span>zone<span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"><span class="token keyword">function</span> <span class="token function">voyageProfitFactor</span><span class="token punctuation">(</span><span class="token parameter">voyage<span class="token punctuation">,</span> history</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">let</span> result <span class="token operator">=</span> <span class="token number">2</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span>voyage<span class="token punctuation">.</span>zone <span class="token operator">===</span> <span class="token string">"china"</span><span class="token punctuation">)</span> result <span class="token operator">+=</span> <span class="token number">1</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span>voyage<span class="token punctuation">.</span>zone <span class="token operator">===</span> <span class="token string">"east-indies"</span><span class="token punctuation">)</span> result <span class="token operator">+=</span> <span class="token number">1</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span>voyage<span class="token punctuation">.</span>zone <span class="token operator">===</span> <span class="token string">"china"</span> <span class="token operator">&amp;</span>amp<span class="token punctuation">;</span><span class="token operator">&amp;</span>amp<span class="token punctuation">;</span> <span class="token function">hasChina</span><span class="token punctuation">(</span>history<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  result <span class="token operator">+=</span> <span class="token number">3</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">if</span> <span class="token punctuation">(</span>history<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">10</span><span class="token punctuation">)</span> result <span class="token operator">+=</span> <span class="token number">1</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">if</span> <span class="token punctuation">(</span>voyage<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">12</span><span class="token punctuation">)</span> result <span class="token operator">+=</span> <span class="token number">1</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">if</span> <span class="token punctuation">(</span>voyage<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">18</span><span class="token punctuation">)</span> result <span class="token operator">-=</span> <span class="token number">1</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token punctuation">}</span></span>
<span class="line"> <span class="token keyword">else</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token keyword">if</span> <span class="token punctuation">(</span>history<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">8</span><span class="token punctuation">)</span> result <span class="token operator">+=</span> <span class="token number">1</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">if</span> <span class="token punctuation">(</span>voyage<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">14</span><span class="token punctuation">)</span> result <span class="token operator">-=</span> <span class="token number">1</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token punctuation">}</span></span>
<span class="line"> <span class="token keyword">return</span> result<span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>我会用继承和多态将处理“中国因素”的逻辑从基础逻辑中分离出来。如果还要引入更多的特殊逻辑，这个重构就很有用——这些重复的“中国因素”会混淆视听，让基础逻辑难以理解。</p>
<p>起初代码里只有一堆函数，如果要引入多态的话，我需要先建立一个类结构，因此我首先使用函数组合成类（144）。这一步重构的结果如下所示：</p>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">function</span> <span class="token function">rating</span><span class="token punctuation">(</span><span class="token parameter">voyage<span class="token punctuation">,</span> history</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">Rating</span><span class="token punctuation">(</span>voyage<span class="token punctuation">,</span> history<span class="token punctuation">)</span><span class="token punctuation">.</span>value<span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span>
<span class="line"><span class="token keyword">class</span> <span class="token class-name">Rating</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token function">constructor</span><span class="token punctuation">(</span><span class="token parameter">voyage<span class="token punctuation">,</span> history</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token keyword">this</span><span class="token punctuation">.</span>voyage <span class="token operator">=</span> voyage<span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">this</span><span class="token punctuation">.</span>history <span class="token operator">=</span> history<span class="token punctuation">;</span></span>
<span class="line"> <span class="token punctuation">}</span></span>
<span class="line"> <span class="token keyword">get</span> <span class="token function">value</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token keyword">const</span> vpf <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>voyageProfitFactor<span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">const</span> vr <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>voyageRisk<span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">const</span> chr <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>captainHistoryRisk<span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">if</span> <span class="token punctuation">(</span>vpf <span class="token operator">*</span> <span class="token number">3</span> <span class="token operator">></span> <span class="token punctuation">(</span>vr <span class="token operator">+</span> chr <span class="token operator">*</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token string">"A"</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">else</span> <span class="token keyword">return</span> <span class="token string">"B"</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token punctuation">}</span></span>
<span class="line"> <span class="token keyword">get</span> <span class="token function">voyageRisk</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token keyword">let</span> result <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>voyage<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">4</span><span class="token punctuation">)</span> result <span class="token operator">+=</span> <span class="token number">2</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>voyage<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">8</span><span class="token punctuation">)</span> result <span class="token operator">+=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>voyage<span class="token punctuation">.</span>length <span class="token operator">-</span> <span class="token number">8</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token string">"china"</span><span class="token punctuation">,</span> <span class="token string">"east-indies"</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">includes</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>voyage<span class="token punctuation">.</span>zone<span class="token punctuation">)</span><span class="token punctuation">)</span> result <span class="token operator">+=</span> <span class="token number">4</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">return</span> Math<span class="token punctuation">.</span><span class="token function">max</span><span class="token punctuation">(</span>result<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token punctuation">}</span></span>
<span class="line"> <span class="token keyword">get</span> <span class="token function">captainHistoryRisk</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token keyword">let</span> result <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>history<span class="token punctuation">.</span>length <span class="token operator">&lt;</span> <span class="token number">5</span><span class="token punctuation">)</span> result <span class="token operator">+=</span> <span class="token number">4</span><span class="token punctuation">;</span></span>
<span class="line">  result <span class="token operator">+=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>history<span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span><span class="token parameter">v</span> <span class="token operator">=></span> v<span class="token punctuation">.</span>profit <span class="token operator">&lt;</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">.</span>length<span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>voyage<span class="token punctuation">.</span>zone <span class="token operator">===</span> <span class="token string">"china"</span> <span class="token operator">&amp;</span>amp<span class="token punctuation">;</span><span class="token operator">&amp;</span>amp<span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>hasChinaHistory<span class="token punctuation">)</span> result <span class="token operator">-=</span> <span class="token number">2</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">return</span> Math<span class="token punctuation">.</span><span class="token function">max</span><span class="token punctuation">(</span>result<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token punctuation">}</span></span>
<span class="line"> <span class="token keyword">get</span> <span class="token function">voyageProfitFactor</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token keyword">let</span> result <span class="token operator">=</span> <span class="token number">2</span><span class="token punctuation">;</span></span>
<span class="line"></span>
<span class="line">  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>voyage<span class="token punctuation">.</span>zone <span class="token operator">===</span> <span class="token string">"china"</span><span class="token punctuation">)</span> result <span class="token operator">+=</span> <span class="token number">1</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>voyage<span class="token punctuation">.</span>zone <span class="token operator">===</span> <span class="token string">"east-indies"</span><span class="token punctuation">)</span> result <span class="token operator">+=</span> <span class="token number">1</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>voyage<span class="token punctuation">.</span>zone <span class="token operator">===</span> <span class="token string">"china"</span> <span class="token operator">&amp;</span>amp<span class="token punctuation">;</span><span class="token operator">&amp;</span>amp<span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>hasChinaHistory<span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">   result <span class="token operator">+=</span> <span class="token number">3</span><span class="token punctuation">;</span></span>
<span class="line">   <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>history<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">10</span><span class="token punctuation">)</span> result <span class="token operator">+=</span> <span class="token number">1</span><span class="token punctuation">;</span></span>
<span class="line">   <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>voyage<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">12</span><span class="token punctuation">)</span> result <span class="token operator">+=</span> <span class="token number">1</span><span class="token punctuation">;</span></span>
<span class="line">   <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>voyage<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">18</span><span class="token punctuation">)</span> result <span class="token operator">-=</span> <span class="token number">1</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token punctuation">}</span></span>
<span class="line">  <span class="token keyword">else</span> <span class="token punctuation">{</span></span>
<span class="line">   <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>history<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">8</span><span class="token punctuation">)</span> result <span class="token operator">+=</span> <span class="token number">1</span><span class="token punctuation">;</span></span>
<span class="line">   <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>voyage<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">14</span><span class="token punctuation">)</span> result <span class="token operator">-=</span> <span class="token number">1</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token punctuation">}</span></span>
<span class="line">  <span class="token keyword">return</span> result<span class="token punctuation">;</span></span>
<span class="line"> <span class="token punctuation">}</span></span>
<span class="line"> <span class="token keyword">get</span> <span class="token function">hasChinaHistory</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span>history<span class="token punctuation">.</span><span class="token function">some</span><span class="token punctuation">(</span><span class="token parameter">v</span> <span class="token operator">=></span> <span class="token string">"china"</span> <span class="token operator">===</span> v<span class="token punctuation">.</span>zone<span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token punctuation">}</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>于是我就有了一个类，用来安放基础逻辑。现在我需要另建一个空的子类，用来安放与超类不同的行为。</p>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">class</span> <span class="token class-name">ExperiencedChinaRating</span> <span class="token keyword">extends</span> <span class="token class-name">Rating</span> <span class="token punctuation">{</span><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><p>然后，建立一个工厂函数，用于在需要时返回变体类。</p>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">function</span> <span class="token function">createRating</span><span class="token punctuation">(</span><span class="token parameter">voyage<span class="token punctuation">,</span> history</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span>voyage<span class="token punctuation">.</span>zone <span class="token operator">===</span> <span class="token string">"china"</span> <span class="token operator">&amp;</span>amp<span class="token punctuation">;</span><span class="token operator">&amp;</span>amp<span class="token punctuation">;</span> history<span class="token punctuation">.</span><span class="token function">some</span><span class="token punctuation">(</span><span class="token parameter">v</span> <span class="token operator">=></span> <span class="token string">"china"</span> <span class="token operator">===</span> v<span class="token punctuation">.</span>zone<span class="token punctuation">)</span><span class="token punctuation">)</span></span>
<span class="line">  <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">ExperiencedChinaRating</span><span class="token punctuation">(</span>voyage<span class="token punctuation">,</span> history<span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">else</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">Rating</span><span class="token punctuation">(</span>voyage<span class="token punctuation">,</span> history<span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>我需要修改所有调用方代码，让它们使用该工厂函数，而不要直接调用构造函数。还好现在调用构造函数的只有 rating 函数一处。</p>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">function</span> <span class="token function">rating</span><span class="token punctuation">(</span><span class="token parameter">voyage<span class="token punctuation">,</span> history</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token keyword">return</span> <span class="token function">createRating</span><span class="token punctuation">(</span>voyage<span class="token punctuation">,</span> history<span class="token punctuation">)</span><span class="token punctuation">.</span>value<span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>有两处行为需要移入子类中。我先处理 captainHistoryRisk 中的逻辑。</p>
<h4 id="class-rating" tabindex="-1"><a class="header-anchor" href="#class-rating"><span>class Rating...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">get</span> <span class="token function">captainHistoryRisk</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">let</span> result <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>history<span class="token punctuation">.</span>length <span class="token operator">&lt;</span> <span class="token number">5</span><span class="token punctuation">)</span> result <span class="token operator">+=</span> <span class="token number">4</span><span class="token punctuation">;</span></span>
<span class="line"> result <span class="token operator">+=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>history<span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span><span class="token parameter">v</span> <span class="token operator">=></span> v<span class="token punctuation">.</span>profit <span class="token operator">&lt;</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">.</span>length<span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>voyage<span class="token punctuation">.</span>zone <span class="token operator">===</span> <span class="token string">"china"</span> <span class="token operator">&amp;</span>amp<span class="token punctuation">;</span><span class="token operator">&amp;</span>amp<span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>hasChinaHistory<span class="token punctuation">)</span> result <span class="token operator">-=</span> <span class="token number">2</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">return</span> Math<span class="token punctuation">.</span><span class="token function">max</span><span class="token punctuation">(</span>result<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>在子类中覆写这个函数。</p>
<p>class ExperiencedChinaRating</p>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">get</span> <span class="token function">captainHistoryRisk</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token keyword">const</span> result <span class="token operator">=</span> <span class="token keyword">super</span><span class="token punctuation">.</span>captainHistoryRisk <span class="token operator">-</span> <span class="token number">2</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">return</span> Math<span class="token punctuation">.</span><span class="token function">max</span><span class="token punctuation">(</span>result<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h4 id="class-rating-1" tabindex="-1"><a class="header-anchor" href="#class-rating-1"><span>class Rating...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">get</span> <span class="token function">captainHistoryRisk</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">let</span> result <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>history<span class="token punctuation">.</span>length <span class="token operator">&lt;</span> <span class="token number">5</span><span class="token punctuation">)</span> result <span class="token operator">+=</span> <span class="token number">4</span><span class="token punctuation">;</span></span>
<span class="line"> result <span class="token operator">+=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>history<span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span><span class="token parameter">v</span> <span class="token operator">=></span> v<span class="token punctuation">.</span>profit <span class="token operator">&lt;</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">.</span>length<span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>voyage<span class="token punctuation">.</span>zone <span class="token operator">===</span> <span class="token string">"china"</span> <span class="token operator">&amp;</span>amp<span class="token punctuation">;</span><span class="token operator">&amp;</span>amp<span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>hasChinaHistory<span class="token punctuation">)</span> result <span class="token operator">-=</span> <span class="token number">2</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">return</span> Math<span class="token punctuation">.</span><span class="token function">max</span><span class="token punctuation">(</span>result<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>分离 voyageProfitFactor 函数中的变体行为要更麻烦一些。我不能直接从超类中删掉变体行为，因为在超类中还有另一条执行路径。我又不想把整个超类中的函数复制到子类中。</p>
<h4 id="class-rating-2" tabindex="-1"><a class="header-anchor" href="#class-rating-2"><span>class Rating...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">get</span> <span class="token function">voyageProfitFactor</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">let</span> result <span class="token operator">=</span> <span class="token number">2</span><span class="token punctuation">;</span></span>
<span class="line"></span>
<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>voyage<span class="token punctuation">.</span>zone <span class="token operator">===</span> <span class="token string">"china"</span><span class="token punctuation">)</span> result <span class="token operator">+=</span> <span class="token number">1</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>voyage<span class="token punctuation">.</span>zone <span class="token operator">===</span> <span class="token string">"east-indies"</span><span class="token punctuation">)</span> result <span class="token operator">+=</span> <span class="token number">1</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>voyage<span class="token punctuation">.</span>zone <span class="token operator">===</span> <span class="token string">"china"</span> <span class="token operator">&amp;</span>amp<span class="token punctuation">;</span><span class="token operator">&amp;</span>amp<span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>hasChinaHistory<span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  result <span class="token operator">+=</span> <span class="token number">3</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>history<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">10</span><span class="token punctuation">)</span> result <span class="token operator">+=</span> <span class="token number">1</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>voyage<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">12</span><span class="token punctuation">)</span> result <span class="token operator">+=</span> <span class="token number">1</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>voyage<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">18</span><span class="token punctuation">)</span> result <span class="token operator">-=</span> <span class="token number">1</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token punctuation">}</span></span>
<span class="line"> <span class="token keyword">else</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>history<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">8</span><span class="token punctuation">)</span> result <span class="token operator">+=</span> <span class="token number">1</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>voyage<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">14</span><span class="token punctuation">)</span> result <span class="token operator">-=</span> <span class="token number">1</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token punctuation">}</span></span>
<span class="line"> <span class="token keyword">return</span> result<span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>所以我先用提炼函数（106）将整个条件逻辑块提炼出来。</p>
<h4 id="class-rating-3" tabindex="-1"><a class="header-anchor" href="#class-rating-3"><span>class Rating...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">get</span> <span class="token function">voyageProfitFactor</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">let</span> result <span class="token operator">=</span> <span class="token number">2</span><span class="token punctuation">;</span></span>
<span class="line"></span>
<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>voyage<span class="token punctuation">.</span>zone <span class="token operator">===</span> <span class="token string">"china"</span><span class="token punctuation">)</span> result <span class="token operator">+=</span> <span class="token number">1</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>voyage<span class="token punctuation">.</span>zone <span class="token operator">===</span> <span class="token string">"east-indies"</span><span class="token punctuation">)</span> result <span class="token operator">+=</span> <span class="token number">1</span><span class="token punctuation">;</span></span>
<span class="line"> result <span class="token operator">+=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>voyageAndHistoryLengthFactor<span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">return</span> result<span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"><span class="token keyword">get</span> <span class="token function">voyageAndHistoryLengthFactor</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">let</span> result <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>voyage<span class="token punctuation">.</span>zone <span class="token operator">===</span> <span class="token string">"china"</span> <span class="token operator">&amp;</span>amp<span class="token punctuation">;</span><span class="token operator">&amp;</span>amp<span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>hasChinaHistory<span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  result <span class="token operator">+=</span> <span class="token number">3</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>history<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">10</span><span class="token punctuation">)</span> result <span class="token operator">+=</span> <span class="token number">1</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>voyage<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">12</span><span class="token punctuation">)</span> result <span class="token operator">+=</span> <span class="token number">1</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>voyage<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">18</span><span class="token punctuation">)</span> result <span class="token operator">-=</span> <span class="token number">1</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token punctuation">}</span></span>
<span class="line"> <span class="token keyword">else</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>history<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">8</span><span class="token punctuation">)</span> result <span class="token operator">+=</span> <span class="token number">1</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>voyage<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">14</span><span class="token punctuation">)</span> result <span class="token operator">-=</span> <span class="token number">1</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token punctuation">}</span></span>
<span class="line"> <span class="token keyword">return</span> result<span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>函数名中出现“And”字样是一个很不好的味道，不过我会暂时容忍它，先聚焦子类化操作。</p>
<h4 id="class-rating-4" tabindex="-1"><a class="header-anchor" href="#class-rating-4"><span>class Rating...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">get</span> <span class="token function">voyageAndHistoryLengthFactor</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">let</span> result <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>history<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">8</span><span class="token punctuation">)</span> result <span class="token operator">+=</span> <span class="token number">1</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>voyage<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">14</span><span class="token punctuation">)</span> result <span class="token operator">-=</span> <span class="token number">1</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">return</span> result<span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h4 id="class-experiencedchinarating" tabindex="-1"><a class="header-anchor" href="#class-experiencedchinarating"><span>class ExperiencedChinaRating...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">get</span> <span class="token function">voyageAndHistoryLengthFactor</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">let</span> result <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span></span>
<span class="line"> result <span class="token operator">+=</span> <span class="token number">3</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>history<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">10</span><span class="token punctuation">)</span> result <span class="token operator">+=</span> <span class="token number">1</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>voyage<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">12</span><span class="token punctuation">)</span> result <span class="token operator">+=</span> <span class="token number">1</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>voyage<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">18</span><span class="token punctuation">)</span> result <span class="token operator">-=</span> <span class="token number">1</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">return</span> result<span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>严格说来，重构到这儿就结束了——我已经把变体行为分离到了子类中，超类的逻辑理解和维护起来更简单了，只有在进入子类代码时我才需要操心变体逻辑。子类的代码表述了它与超类的差异。</p>
<p>但我觉得至少应该谈谈如何处理这个丑陋的新函数。引入一个函数以便子类覆写，这在处理这种“基础和变体”的继承关系时是常见操作。但这样一个难看的函数只会妨碍——而不是帮助——别人理解其中的逻辑。</p>
<p>函数名中的“And”字样说明其中包含了两件事，所以我觉得应该将它们分开。我会用提炼函数（106）把“历史航行数”（history length）的相关逻辑提炼出来。这一步提炼在超类和子类中都要发生，我首先从超类开始。</p>
<h4 id="class-rating-5" tabindex="-1"><a class="header-anchor" href="#class-rating-5"><span>class Rating...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">get</span> <span class="token function">voyageAndHistoryLengthFactor</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">let</span> result <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span></span>
<span class="line"> result <span class="token operator">+=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>historyLengthFactor<span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>voyage<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">14</span><span class="token punctuation">)</span> result <span class="token operator">-=</span> <span class="token number">1</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">return</span> result<span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"><span class="token keyword">get</span> <span class="token function">historyLengthFactor</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>history<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">8</span><span class="token punctuation">)</span> <span class="token operator">?</span> <span class="token number">1</span> <span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>然后在子类中也如法炮制。</p>
<h4 id="class-experiencedchinarating-1" tabindex="-1"><a class="header-anchor" href="#class-experiencedchinarating-1"><span>class ExperiencedChinaRating...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">get</span> <span class="token function">voyageAndHistoryLengthFactor</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">let</span> result <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span></span>
<span class="line"> result <span class="token operator">+=</span> <span class="token number">3</span><span class="token punctuation">;</span></span>
<span class="line"> result <span class="token operator">+=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>historyLengthFactor<span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>voyage<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">12</span><span class="token punctuation">)</span> result <span class="token operator">+=</span> <span class="token number">1</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>voyage<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">18</span><span class="token punctuation">)</span> result <span class="token operator">-=</span> <span class="token number">1</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">return</span> result<span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"><span class="token keyword">get</span> <span class="token function">historyLengthFactor</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>history<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">10</span><span class="token punctuation">)</span> <span class="token operator">?</span> <span class="token number">1</span> <span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>然后在超类中使用搬移语句到调用者（217）。</p>
<h4 id="class-rating-6" tabindex="-1"><a class="header-anchor" href="#class-rating-6"><span>class Rating...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">get</span> <span class="token function">voyageProfitFactor</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">let</span> result <span class="token operator">=</span> <span class="token number">2</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>voyage<span class="token punctuation">.</span>zone <span class="token operator">===</span> <span class="token string">"china"</span><span class="token punctuation">)</span> result <span class="token operator">+=</span> <span class="token number">1</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>voyage<span class="token punctuation">.</span>zone <span class="token operator">===</span> <span class="token string">"east-indies"</span><span class="token punctuation">)</span> result <span class="token operator">+=</span> <span class="token number">1</span><span class="token punctuation">;</span></span>
<span class="line"> result <span class="token operator">+=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>historyLengthFactor<span class="token punctuation">;</span></span>
<span class="line"> result <span class="token operator">+=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>voyageAndHistoryLengthFactor<span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">return</span> result<span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span>
<span class="line"><span class="token keyword">get</span> <span class="token function">voyageAndHistoryLengthFactor</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">let</span> result <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span></span>
<span class="line"> result <span class="token operator">+=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>historyLengthFactor<span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>voyage<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">14</span><span class="token punctuation">)</span> result <span class="token operator">-=</span> <span class="token number">1</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">return</span> result<span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h4 id="class-experiencedchinarating-2" tabindex="-1"><a class="header-anchor" href="#class-experiencedchinarating-2"><span>class ExperiencedChinaRating...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">get</span> <span class="token function">voyageAndHistoryLengthFactor</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">let</span> result <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span></span>
<span class="line"> result <span class="token operator">+=</span> <span class="token number">3</span><span class="token punctuation">;</span></span>
<span class="line"> result <span class="token operator">+=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>historyLengthFactor<span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>voyage<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">12</span><span class="token punctuation">)</span> result <span class="token operator">+=</span> <span class="token number">1</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>voyage<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">18</span><span class="token punctuation">)</span> result <span class="token operator">-=</span> <span class="token number">1</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">return</span> result<span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>再用函数改名（124）改掉这个难听的名字。</p>
<h4 id="class-rating-7" tabindex="-1"><a class="header-anchor" href="#class-rating-7"><span>class Rating...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">get</span> <span class="token function">voyageProfitFactor</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">let</span> result <span class="token operator">=</span> <span class="token number">2</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>voyage<span class="token punctuation">.</span>zone <span class="token operator">===</span> <span class="token string">"china"</span><span class="token punctuation">)</span> result <span class="token operator">+=</span> <span class="token number">1</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>voyage<span class="token punctuation">.</span>zone <span class="token operator">===</span> <span class="token string">"east-indies"</span><span class="token punctuation">)</span> result <span class="token operator">+=</span> <span class="token number">1</span><span class="token punctuation">;</span></span>
<span class="line"> result <span class="token operator">+=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>historyLengthFactor<span class="token punctuation">;</span></span>
<span class="line"> result <span class="token operator">+=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>voyageLengthFactor<span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">return</span> result<span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span>
<span class="line"><span class="token keyword">get</span> <span class="token function">voyageLengthFactor</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>voyage<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">14</span><span class="token punctuation">)</span> <span class="token operator">?</span> <span class="token operator">-</span> <span class="token number">1</span><span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>改为三元表达式，以简化 voyageLengthFactor 函数。</p>
<h4 id="class-experiencedchinarating-3" tabindex="-1"><a class="header-anchor" href="#class-experiencedchinarating-3"><span>class ExperiencedChinaRating...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">get</span> <span class="token function">voyageLengthFactor</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">let</span> result <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span></span>
<span class="line"> result <span class="token operator">+=</span> <span class="token number">3</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>voyage<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">12</span><span class="token punctuation">)</span> result <span class="token operator">+=</span> <span class="token number">1</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>voyage<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">18</span><span class="token punctuation">)</span> result <span class="token operator">-=</span> <span class="token number">1</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token keyword">return</span> result<span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>最后一件事：在“航程数”（voyage length）因素上加上 3 分，我认为这个逻辑不合理，应该把这 3 分加在最终的结果上。</p>
<h4 id="class-experiencedchinarating-4" tabindex="-1"><a class="header-anchor" href="#class-experiencedchinarating-4"><span>class ExperiencedChinaRating...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">get</span> <span class="token function">voyageProfitFactor</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token keyword">return</span> <span class="token keyword">super</span><span class="token punctuation">.</span>voyageProfitFactor <span class="token operator">+</span> <span class="token number">3</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span>
<span class="line"><span class="token keyword">get</span> <span class="token function">voyageLengthFactor</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token keyword">let</span> result <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span></span>
<span class="line">  result <span class="token operator">+=</span> <span class="token number">3</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>voyage<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">12</span><span class="token punctuation">)</span> result <span class="token operator">+=</span> <span class="token number">1</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>voyage<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">18</span><span class="token punctuation">)</span> result <span class="token operator">-=</span> <span class="token number">1</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">return</span> result<span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>重构结束，我得到了如下代码。首先，我有一个基本的 Rating 类，其中不考虑与“中国经验”相关的复杂性：</p>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">class</span> <span class="token class-name">Rating</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token function">constructor</span><span class="token punctuation">(</span><span class="token parameter">voyage<span class="token punctuation">,</span> history</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token keyword">this</span><span class="token punctuation">.</span>voyage <span class="token operator">=</span> voyage<span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">this</span><span class="token punctuation">.</span>history <span class="token operator">=</span> history<span class="token punctuation">;</span></span>
<span class="line"> <span class="token punctuation">}</span></span>
<span class="line"> <span class="token keyword">get</span> <span class="token function">value</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token keyword">const</span> vpf <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>voyageProfitFactor<span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">const</span> vr <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>voyageRisk<span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">const</span> chr <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>captainHistoryRisk<span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">if</span> <span class="token punctuation">(</span>vpf <span class="token operator">*</span> <span class="token number">3</span> <span class="token operator">></span> <span class="token punctuation">(</span>vr <span class="token operator">+</span> chr <span class="token operator">*</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token string">"A"</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">else</span> <span class="token keyword">return</span> <span class="token string">"B"</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token punctuation">}</span></span>
<span class="line"> <span class="token keyword">get</span> <span class="token function">voyageRisk</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token keyword">let</span> result <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>voyage<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">4</span><span class="token punctuation">)</span> result <span class="token operator">+=</span> <span class="token number">2</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>voyage<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">8</span><span class="token punctuation">)</span> result <span class="token operator">+=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>voyage<span class="token punctuation">.</span>length <span class="token operator">-</span> <span class="token number">8</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token string">"china"</span><span class="token punctuation">,</span> <span class="token string">"east-indies"</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">includes</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>voyage<span class="token punctuation">.</span>zone<span class="token punctuation">)</span><span class="token punctuation">)</span> result <span class="token operator">+=</span> <span class="token number">4</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">return</span> Math<span class="token punctuation">.</span><span class="token function">max</span><span class="token punctuation">(</span>result<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token punctuation">}</span></span>
<span class="line"> <span class="token keyword">get</span> <span class="token function">captainHistoryRisk</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token keyword">let</span> result <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>history<span class="token punctuation">.</span>length <span class="token operator">&lt;</span> <span class="token number">5</span><span class="token punctuation">)</span> result <span class="token operator">+=</span> <span class="token number">4</span><span class="token punctuation">;</span></span>
<span class="line">  result <span class="token operator">+=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>history<span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span><span class="token parameter">v</span> <span class="token operator">=></span> v<span class="token punctuation">.</span>profit <span class="token operator">&lt;</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">.</span>length<span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">return</span> Math<span class="token punctuation">.</span><span class="token function">max</span><span class="token punctuation">(</span>result<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token punctuation">}</span></span>
<span class="line"> <span class="token keyword">get</span> <span class="token function">voyageProfitFactor</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token keyword">let</span> result <span class="token operator">=</span> <span class="token number">2</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>voyage<span class="token punctuation">.</span>zone <span class="token operator">===</span> <span class="token string">"china"</span><span class="token punctuation">)</span> result <span class="token operator">+=</span> <span class="token number">1</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>voyage<span class="token punctuation">.</span>zone <span class="token operator">===</span> <span class="token string">"east-indies"</span><span class="token punctuation">)</span> result <span class="token operator">+=</span> <span class="token number">1</span><span class="token punctuation">;</span></span>
<span class="line">  result <span class="token operator">+=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>historyLengthFactor<span class="token punctuation">;</span></span>
<span class="line">  result <span class="token operator">+=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>voyageLengthFactor<span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">return</span> result<span class="token punctuation">;</span></span>
<span class="line"> <span class="token punctuation">}</span></span>
<span class="line"> <span class="token keyword">get</span> <span class="token function">voyageLengthFactor</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>voyage<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">14</span><span class="token punctuation">)</span> <span class="token operator">?</span> <span class="token operator">-</span> <span class="token number">1</span><span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token punctuation">}</span></span>
<span class="line"> <span class="token keyword">get</span> <span class="token function">historyLengthFactor</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>history<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">8</span><span class="token punctuation">)</span> <span class="token operator">?</span> <span class="token number">1</span> <span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token punctuation">}</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>与“中国经验”相关的代码则清晰表述出在基本逻辑之上的一系列变体逻辑：</p>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">class</span> <span class="token class-name">ExperiencedChinaRating</span> <span class="token keyword">extends</span> <span class="token class-name">Rating</span> <span class="token punctuation">{</span></span>
<span class="line"> <span class="token keyword">get</span> <span class="token function">captainHistoryRisk</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token keyword">const</span> result <span class="token operator">=</span> <span class="token keyword">super</span><span class="token punctuation">.</span>captainHistoryRisk <span class="token operator">-</span> <span class="token number">2</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">return</span> Math<span class="token punctuation">.</span><span class="token function">max</span><span class="token punctuation">(</span>result<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token punctuation">}</span></span>
<span class="line"> <span class="token keyword">get</span> <span class="token function">voyageLengthFactor</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token keyword">let</span> result <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>voyage<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">12</span><span class="token punctuation">)</span> result <span class="token operator">+=</span> <span class="token number">1</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>voyage<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">18</span><span class="token punctuation">)</span> result <span class="token operator">-=</span> <span class="token number">1</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">return</span> result<span class="token punctuation">;</span></span>
<span class="line"> <span class="token punctuation">}</span></span>
<span class="line"> <span class="token keyword">get</span> <span class="token function">historyLengthFactor</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>history<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">10</span><span class="token punctuation">)</span> <span class="token operator">?</span> <span class="token number">1</span> <span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token punctuation">}</span></span>
<span class="line"> <span class="token keyword">get</span> <span class="token function">voyageProfitFactor</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token keyword">return</span> <span class="token keyword">super</span><span class="token punctuation">.</span>voyageProfitFactor <span class="token operator">+</span> <span class="token number">3</span><span class="token punctuation">;</span></span>
<span class="line"> <span class="token punctuation">}</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2 id="_10-5-引入特例-introduce-special-case" tabindex="-1"><a class="header-anchor" href="#_10-5-引入特例-introduce-special-case"><span>10.5 引入特例（Introduce Special Case）</span></a></h2>
<p>曾用名：引入 Null 对象（Introduce Null Object）</p>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">if</span> <span class="token punctuation">(</span>aCustomer <span class="token operator">===</span> <span class="token string">"unknown"</span><span class="token punctuation">)</span> customerName <span class="token operator">=</span> <span class="token string">"occupant"</span><span class="token punctuation">;</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span class="token keyword">class</span> <span class="token class-name">UnknownCustomer</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token keyword">get</span> <span class="token function">name</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token keyword">return</span> <span class="token string">"occupant"</span><span class="token punctuation">;</span><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3 id="动机-4" tabindex="-1"><a class="header-anchor" href="#动机-4"><span>动机</span></a></h3>
<p>一种常见的重复代码是这种情况：一个数据结构的使用者都在检查某个特殊的值，并且当这个特殊值出现时所做的处理也都相同。如果我发现代码库中有多处以同样方式应对同一个特殊值，我就会想要把这个处理逻辑收拢到一处。</p>
<p>处理这种情况的一个好办法是使用“特例”（Special Case）模式：创建一个特例元素，用以表达对这种特例的共用行为的处理。这样我就可以用一个函数调用取代大部分特例检查逻辑。</p>
<p>特例有几种表现形式。如果我只需要从这个对象读取数据，可以提供一个字面量对象（literal object），其中所有的值都是预先填充好的。如果除简单的数值之外还需要更多的行为，就需要创建一个特殊对象，其中包含所有共用行为所对应的函数。特例对象可以由一个封装类来返回，也可以通过变换插入一个数据结构。</p>
<p>一个通常需要特例处理的值就是 null，这也是这个模式常被叫作“Null 对象”（Null Object）模式的原因——我喜欢说：Null 对象是特例的一种特例。</p>
<h3 id="做法-4" tabindex="-1"><a class="header-anchor" href="#做法-4"><span>做法</span></a></h3>
<p>我们从一个作为容器的数据结构（或者类）开始，其中包含一个属性，该属性就是我们要重构的目标。容器的客户端每次使用这个属性时，都需要将其与某个特例值做比对。我们希望把这个特例值替换为代表这种特例情况的类或数据结构。</p>
<p>给重构目标添加检查特例的属性，令其返回 false。</p>
<p>创建一个特例对象，其中只有检查特例的属性，返回 true。</p>
<p>对“与特例值做比对”的代码运用提炼函数（106），确保所有客户端都使用这个新函数，而不再直接做特例值的比对。</p>
<p>将新的特例对象引入代码中，可以从函数调用中返回，也可以在变换函数中生成。</p>
<p>修改特例比对函数的主体，在其中直接使用检查特例的属性。</p>
<p>测试。</p>
<p>使用函数组合成类（144）或函数组合成变换（149），把通用的特例处理逻辑都搬移到新建的特例对象中。</p>
<p>特例类对于简单的请求通常会返回固定的值，因此可以将其实现为字面记录（literal record）。</p>
<p>对特例比对函数使用内联函数（115），将其内联到仍然需要的地方。</p>
<h3 id="范例-4" tabindex="-1"><a class="header-anchor" href="#范例-4"><span>范例</span></a></h3>
<p>一家提供公共事业服务的公司将自己的服务安装在各个场所（site）。</p>
<h4 id="class-site" tabindex="-1"><a class="header-anchor" href="#class-site"><span>class Site...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">get</span> <span class="token function">customer</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span>_customer<span class="token punctuation">;</span><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><p>代表“顾客”的 Customer 类有多个属性，我只考虑其中 3 个。</p>
<h4 id="class-customer" tabindex="-1"><a class="header-anchor" href="#class-customer"><span>class Customer...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">get</span> <span class="token function">name</span><span class="token punctuation">(</span><span class="token punctuation">)</span>           <span class="token punctuation">{</span><span class="token operator">...</span><span class="token punctuation">}</span></span>
<span class="line"><span class="token keyword">get</span> <span class="token function">billingPlan</span><span class="token punctuation">(</span><span class="token punctuation">)</span>    <span class="token punctuation">{</span><span class="token operator">...</span><span class="token punctuation">}</span></span>
<span class="line"><span class="token keyword">set</span> <span class="token function">billingPlan</span><span class="token punctuation">(</span><span class="token parameter">arg</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token operator">...</span><span class="token punctuation">}</span></span>
<span class="line"><span class="token keyword">get</span> <span class="token function">paymentHistory</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token operator">...</span><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>大多数情况下，一个场所会对应一个顾客，但有些场所没有与之对应的顾客，可能是因为之前的住户搬走了，而新搬来的住户我还不知道是谁。这种情况下，数据记录中的 customer 字段会被填充为字符串&quot;unknown&quot;。因为这种情况时有发生，所以 Site 对象的客户端必须有办法处理“顾客未知”的情况。下面是一些示例代码片段。</p>
<h4 id="客户端-1" tabindex="-1"><a class="header-anchor" href="#客户端-1"><span>客户端 1...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">const</span> aCustomer <span class="token operator">=</span> site<span class="token punctuation">.</span>customer<span class="token punctuation">;</span></span>
<span class="line"><span class="token comment">// ... lots of intervening code ...</span></span>
<span class="line"><span class="token keyword">let</span> customerName<span class="token punctuation">;</span></span>
<span class="line"><span class="token keyword">if</span> <span class="token punctuation">(</span>aCustomer <span class="token operator">===</span> <span class="token string">"unknown"</span><span class="token punctuation">)</span> customerName <span class="token operator">=</span> <span class="token string">"occupant"</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token keyword">else</span> customerName <span class="token operator">=</span> aCustomer<span class="token punctuation">.</span>name<span class="token punctuation">;</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h4 id="客户端-2" tabindex="-1"><a class="header-anchor" href="#客户端-2"><span>客户端 2...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">const</span> plan <span class="token operator">=</span></span>
<span class="line">  aCustomer <span class="token operator">===</span> <span class="token string">"unknown"</span> <span class="token operator">?</span> registry<span class="token punctuation">.</span>billingPlans<span class="token punctuation">.</span>basic <span class="token operator">:</span> aCustomer<span class="token punctuation">.</span>billingPlan<span class="token punctuation">;</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div></div></div><h4 id="客户端-3" tabindex="-1"><a class="header-anchor" href="#客户端-3"><span>客户端 3...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">if</span> <span class="token punctuation">(</span>aCustomer <span class="token operator">!==</span> <span class="token string">"unknown"</span><span class="token punctuation">)</span> aCustomer<span class="token punctuation">.</span>billingPlan <span class="token operator">=</span> newPlan<span class="token punctuation">;</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><h4 id="客户端-4" tabindex="-1"><a class="header-anchor" href="#客户端-4"><span>客户端 4...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">const</span> weeksDelinquent <span class="token operator">=</span></span>
<span class="line">  aCustomer <span class="token operator">===</span> <span class="token string">"unknown"</span></span>
<span class="line">    <span class="token operator">?</span> <span class="token number">0</span></span>
<span class="line">    <span class="token operator">:</span> aCustomer<span class="token punctuation">.</span>paymentHistory<span class="token punctuation">.</span>weeksDelinquentInLastYear<span class="token punctuation">;</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>浏览整个代码库，我看到有很多使用 Site 对象的客户端在处理“顾客未知”的情况，大多数都用了同样的应对方式：用&quot;occupant&quot;（居民）作为顾客名，使用基本的计价套餐，并认为这家顾客没有欠费。到处都在检查这种特例，再加上对特例的处理方式高度一致，这些现象告诉我：是时候使用特例对象（Special Case Object）模式了。</p>
<p>我首先给 Customer 添加一个函数，用于指示“这个顾客是否未知”。</p>
<h4 id="class-customer-1" tabindex="-1"><a class="header-anchor" href="#class-customer-1"><span>class Customer...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">get</span> <span class="token function">isUnknown</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><p>然后我给“未知的顾客”专门创建一个类。</p>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">class</span> <span class="token class-name">UnknownCustomer</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token keyword">get</span> <span class="token function">isUnknown</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">    <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token punctuation">}</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>注意，我没有把 UnknownCustomer 类声明为 Customer 的子类。在其他编程语言（尤其是静态类型的编程语言）中，我会需要继承关系。但 JavaScript 是一种动态类型语言，按照它的子类化规则，这里不声明继承关系反而更好。</p>
<p>下面就是麻烦之处了。我必须在所有期望得到&quot;unknown&quot;值的地方返回这个新的特例对象，并修改所有检查&quot;unknown&quot;值的地方，令其使用新的 isUnknown 函数。一般而言，我总是希望细心安排修改过程，使我可以每次做一点小修改，然后马上测试。但如果我修改了 Customer 类，使其返回 UnknownCustomer 对象（而非&quot;unknown&quot;字符串），那么就必须同时修改所有客户端，让它们不要检查&quot;unknown&quot;字符串，而是调用 isUnknown 函数——这两个修改必须一次完成。我感觉这一大步修改就像一大块难吃的食物一样难以下咽。</p>
<p>还好，遇到这种困境时，有一个常用的技巧可以帮忙。如果有一段代码需要在很多地方做修改（例如我们这里的“与特例做比对”的代码），我会先对其使用提炼函数（106）。</p>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">function</span> <span class="token function">isUnknown</span><span class="token punctuation">(</span><span class="token parameter">arg</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token punctuation">(</span>arg <span class="token keyword">instanceof</span> <span class="token class-name">Customer</span> <span class="token operator">||</span> arg <span class="token operator">===</span> <span class="token string">"unknown"</span><span class="token punctuation">)</span><span class="token punctuation">)</span></span>
<span class="line">    <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">investigate bad value: &lt;</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>arg<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">return</span> arg <span class="token operator">===</span> <span class="token string">"unknown"</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>我会放一个陷阱，捕捉意料之外的值。如果在重构过程中我犯了错误，引入了奇怪的行为，这个陷阱会帮我发现。</p>
<p>现在，凡是检查未知顾客的地方，都可以改用这个函数了。我可以逐一修改这些地方，每次修改之后都可以执行测试。</p>
<h4 id="客户端-1-1" tabindex="-1"><a class="header-anchor" href="#客户端-1-1"><span>客户端 1...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">let</span> customerName<span class="token punctuation">;</span></span>
<span class="line"><span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">isUnknown</span><span class="token punctuation">(</span>aCustomer<span class="token punctuation">)</span><span class="token punctuation">)</span> customerName <span class="token operator">=</span> <span class="token string">"occupant"</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token keyword">else</span> customerName <span class="token operator">=</span> aCustomer<span class="token punctuation">.</span>name<span class="token punctuation">;</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>没用多久，就全部修改完了。</p>
<h4 id="客户端-2-1" tabindex="-1"><a class="header-anchor" href="#客户端-2-1"><span>客户端 2...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">const</span> plan <span class="token operator">=</span> <span class="token function">isUnknown</span><span class="token punctuation">(</span>aCustomer<span class="token punctuation">)</span></span>
<span class="line">  <span class="token operator">?</span> registry<span class="token punctuation">.</span>billingPlans<span class="token punctuation">.</span>basic</span>
<span class="line">  <span class="token operator">:</span> aCustomer<span class="token punctuation">.</span>billingPlan<span class="token punctuation">;</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h4 id="客户端-3-1" tabindex="-1"><a class="header-anchor" href="#客户端-3-1"><span>客户端 3...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">isUnknown</span><span class="token punctuation">(</span>aCustomer<span class="token punctuation">)</span><span class="token punctuation">)</span> aCustomer<span class="token punctuation">.</span>billingPlan <span class="token operator">=</span> newPlan<span class="token punctuation">;</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><h4 id="客户端-4-1" tabindex="-1"><a class="header-anchor" href="#客户端-4-1"><span>客户端 4...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">const</span> weeksDelinquent <span class="token operator">=</span> <span class="token function">isUnknown</span><span class="token punctuation">(</span>aCustomer<span class="token punctuation">)</span></span>
<span class="line">  <span class="token operator">?</span> <span class="token number">0</span></span>
<span class="line">  <span class="token operator">:</span> aCustomer<span class="token punctuation">.</span>paymentHistory<span class="token punctuation">.</span>weeksDelinquentInLastYear<span class="token punctuation">;</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>将所有调用处都改为使用 isUnknown 函数之后，就可以修改 Site 类，令其在顾客未知时返回 UnknownCustomer 对象。</p>
<h4 id="class-site-1" tabindex="-1"><a class="header-anchor" href="#class-site-1"><span>class Site...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">get</span> <span class="token function">customer</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>_customer <span class="token operator">===</span> <span class="token string">"unknown"</span><span class="token punctuation">)</span> <span class="token operator">?</span> <span class="token keyword">new</span> <span class="token class-name">UnknownCustomer</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">:</span> <span class="token keyword">this</span><span class="token punctuation">.</span>_customer<span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>然后修改 isUnknown 函数的判断逻辑。做完这步修改之后我可以做一次全文搜索，应该没有任何地方使用&quot;unknown&quot;字符串了。</p>
<h4 id="客户端-1-2" tabindex="-1"><a class="header-anchor" href="#客户端-1-2"><span>客户端 1...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">function</span> <span class="token function">isUnknown</span><span class="token punctuation">(</span><span class="token parameter">arg</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token punctuation">(</span>arg <span class="token keyword">instanceof</span> <span class="token class-name">Customer</span> <span class="token operator">||</span> arg <span class="token keyword">instanceof</span> <span class="token class-name">UnknownCustomer</span><span class="token punctuation">)</span><span class="token punctuation">)</span></span>
<span class="line">    <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">investigate bad value: &lt;</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>arg<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">return</span> arg<span class="token punctuation">.</span>isUnknown<span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>测试，以确保一切运转如常。</p>
<p>现在，有趣的部分开始了。我可以逐一查看客户端检查特例的代码，看它们处理特例的逻辑，并考虑是否能用函数组合成类（144）将其替换为一个共同的、符合预期的值。此刻，有多处客户端代码用字符串&quot;occupant&quot;来作为未知顾客的名字，就像下面这样。</p>
<h4 id="客户端-1-3" tabindex="-1"><a class="header-anchor" href="#客户端-1-3"><span>客户端 1...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">let</span> customerName<span class="token punctuation">;</span></span>
<span class="line"><span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">isUnknown</span><span class="token punctuation">(</span>aCustomer<span class="token punctuation">)</span><span class="token punctuation">)</span> customerName <span class="token operator">=</span> <span class="token string">"occupant"</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token keyword">else</span> customerName <span class="token operator">=</span> aCustomer<span class="token punctuation">.</span>name<span class="token punctuation">;</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>我可以在 UnknownCustomer 类中添加一个合适的函数。</p>
<h4 id="class-unknowncustomer" tabindex="-1"><a class="header-anchor" href="#class-unknowncustomer"><span>class UnknownCustomer...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">get</span> <span class="token function">name</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token keyword">return</span> <span class="token string">"occupant"</span><span class="token punctuation">;</span><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><p>然后我就可以去掉所有条件代码。</p>
<h4 id="客户端-1-4" tabindex="-1"><a class="header-anchor" href="#客户端-1-4"><span>客户端 1...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">const</span> customerName <span class="token operator">=</span> aCustomer<span class="token punctuation">.</span>name<span class="token punctuation">;</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><p>测试通过之后，我可能会用内联变量（123）把 customerName 变量也消除掉。</p>
<p>接下来处理代表“计价套餐”的 billingPlan 属性。</p>
<h4 id="客户端-2-2" tabindex="-1"><a class="header-anchor" href="#客户端-2-2"><span>客户端 2...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">const</span> plan <span class="token operator">=</span> <span class="token function">isUnknown</span><span class="token punctuation">(</span>aCustomer<span class="token punctuation">)</span></span>
<span class="line">  <span class="token operator">?</span> registry<span class="token punctuation">.</span>billingPlans<span class="token punctuation">.</span>basic</span>
<span class="line">  <span class="token operator">:</span> aCustomer<span class="token punctuation">.</span>billingPlan<span class="token punctuation">;</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h4 id="客户端-3-2" tabindex="-1"><a class="header-anchor" href="#客户端-3-2"><span>客户端 3...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">isUnknown</span><span class="token punctuation">(</span>aCustomer<span class="token punctuation">)</span><span class="token punctuation">)</span> aCustomer<span class="token punctuation">.</span>billingPlan <span class="token operator">=</span> newPlan<span class="token punctuation">;</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><p>对于读取该属性的行为，我的处理方法跟前面处理 name 属性一样——找到通用的应对方式，并在 UnknownCustomer 中使用之。至于对该属性的写操作，当前的代码没有对未知顾客调用过设值函数，所以在特例对象中，我会保留设值函数，但其中什么都不做。</p>
<h4 id="class-unknowncustomer-1" tabindex="-1"><a class="header-anchor" href="#class-unknowncustomer-1"><span>class UnknownCustomer...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">get</span> <span class="token function">billingPlan</span><span class="token punctuation">(</span><span class="token punctuation">)</span>  <span class="token punctuation">{</span><span class="token keyword">return</span> registry<span class="token punctuation">.</span>billingPlans<span class="token punctuation">.</span>basic<span class="token punctuation">;</span><span class="token punctuation">}</span></span>
<span class="line"><span class="token keyword">set</span> <span class="token function">billingPlan</span><span class="token punctuation">(</span><span class="token parameter">arg</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">/* ignore */</span> <span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div></div></div><h4 id="读取的例子" tabindex="-1"><a class="header-anchor" href="#读取的例子"><span>读取的例子...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">const</span> plan <span class="token operator">=</span> aCustomer<span class="token punctuation">.</span>billingPlan<span class="token punctuation">;</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><h4 id="更新的例子" tabindex="-1"><a class="header-anchor" href="#更新的例子"><span>更新的例子...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line">aCustomer<span class="token punctuation">.</span>billingPlan <span class="token operator">=</span> newPlan<span class="token punctuation">;</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><p>特例对象是值对象，因此应该始终是不可变的，即便它们替代的原对象本身是可变的。</p>
<p>最后一个例子则更麻烦一些，因为特例对象需要返回另一个对象，后者又有其自己的属性。</p>
<h4 id="客户端" tabindex="-1"><a class="header-anchor" href="#客户端"><span>客户端...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">const</span> weeksDelinquent <span class="token operator">=</span> <span class="token function">isUnknown</span><span class="token punctuation">(</span>aCustomer<span class="token punctuation">)</span></span>
<span class="line">  <span class="token operator">?</span> <span class="token number">0</span></span>
<span class="line">  <span class="token operator">:</span> aCustomer<span class="token punctuation">.</span>paymentHistory<span class="token punctuation">.</span>weeksDelinquentInLastYear<span class="token punctuation">;</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>一般的原则是：如果特例对象需要返回关联对象，被返回的通常也是特例对象。所以，我需要创建一个代表“空支付记录”的特例类 NullPaymentHistory。</p>
<h4 id="class-unknowncustomer-2" tabindex="-1"><a class="header-anchor" href="#class-unknowncustomer-2"><span>class UnknownCustomer...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">get</span> <span class="token function">paymentHistory</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">NullPaymentHistory</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><h4 id="class-nullpaymenthistory" tabindex="-1"><a class="header-anchor" href="#class-nullpaymenthistory"><span>class NullPaymentHistory...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">get</span> <span class="token function">weeksDelinquentInLastYear</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><h4 id="客户端-5" tabindex="-1"><a class="header-anchor" href="#客户端-5"><span>客户端...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">const</span> weeksDelinquent <span class="token operator">=</span> aCustomer<span class="token punctuation">.</span>paymentHistory<span class="token punctuation">.</span>weeksDelinquentInLastYear<span class="token punctuation">;</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><p>我继续查看客户端代码，寻找是否有能用多态行为取代的地方。但也会有例外情况——客户端不想使用特例对象提供的逻辑，而是想做一些别的处理。我可能有 23 处客户端代码用&quot;occupant&quot;作为未知顾客的名字，但还有一处用了别的值。</p>
<h4 id="客户端-6" tabindex="-1"><a class="header-anchor" href="#客户端-6"><span>客户端...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">const</span> name <span class="token operator">=</span> <span class="token operator">!</span><span class="token function">isUnknown</span><span class="token punctuation">(</span>aCustomer<span class="token punctuation">)</span> <span class="token operator">?</span> aCustomer<span class="token punctuation">.</span>name <span class="token operator">:</span> <span class="token string">"unknown occupant"</span><span class="token punctuation">;</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><p>这种情况下，我只能在客户端保留特例检查的逻辑。我会对其做些修改，让它使用 aCustomer 对象身上的 isUnknown 函数，也就是对全局的 isUnknown 函数使用内联函数（115）。</p>
<h4 id="客户端-7" tabindex="-1"><a class="header-anchor" href="#客户端-7"><span>客户端...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">const</span> name <span class="token operator">=</span> aCustomer<span class="token punctuation">.</span>isUnknown <span class="token operator">?</span> <span class="token string">"unknown occupant"</span> <span class="token operator">:</span> aCustomer<span class="token punctuation">.</span>name<span class="token punctuation">;</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><p>处理完所有客户端代码后，全局的 isUnknown 函数应该没人再调用了，可以用移除死代码（237）将其移除。</p>
<h3 id="范例-使用对象字面量" tabindex="-1"><a class="header-anchor" href="#范例-使用对象字面量"><span>范例：使用对象字面量</span></a></h3>
<p>我们在上面处理的其实是一些很简单的值，却要创建一个这样的类，未免有点儿大动干戈。但在上面这个例子中，我必须创建这样一个类，因为 Customer 类是允许使用者更新其内容的。但如果面对一个只读的数据结构，我就可以改用字面量对象（literal object）。</p>
<p>还是前面这个例子——几乎完全一样，除了一件事：这次没有客户端对 Customer 对象做更新操作：</p>
<h4 id="class-site-2" tabindex="-1"><a class="header-anchor" href="#class-site-2"><span>class Site...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">get</span> <span class="token function">customer</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span>_customer<span class="token punctuation">;</span><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><h4 id="class-customer-2" tabindex="-1"><a class="header-anchor" href="#class-customer-2"><span>class Customer...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">get</span> <span class="token function">name</span><span class="token punctuation">(</span><span class="token punctuation">)</span>           <span class="token punctuation">{</span><span class="token operator">...</span><span class="token punctuation">}</span></span>
<span class="line"><span class="token keyword">get</span> <span class="token function">billingPlan</span><span class="token punctuation">(</span><span class="token punctuation">)</span>    <span class="token punctuation">{</span><span class="token operator">...</span><span class="token punctuation">}</span></span>
<span class="line"><span class="token keyword">set</span> <span class="token function">billingPlan</span><span class="token punctuation">(</span><span class="token parameter">arg</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token operator">...</span><span class="token punctuation">}</span></span>
<span class="line"><span class="token keyword">get</span> <span class="token function">paymentHistory</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token operator">...</span><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h4 id="客户端-1-5" tabindex="-1"><a class="header-anchor" href="#客户端-1-5"><span>客户端 1...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">const</span> aCustomer <span class="token operator">=</span> site<span class="token punctuation">.</span>customer<span class="token punctuation">;</span></span>
<span class="line"><span class="token comment">// ... lots of intervening code ...</span></span>
<span class="line"><span class="token keyword">let</span> customerName<span class="token punctuation">;</span></span>
<span class="line"><span class="token keyword">if</span> <span class="token punctuation">(</span>aCustomer <span class="token operator">===</span> <span class="token string">"unknown"</span><span class="token punctuation">)</span> customerName <span class="token operator">=</span> <span class="token string">"occupant"</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token keyword">else</span> customerName <span class="token operator">=</span> aCustomer<span class="token punctuation">.</span>name<span class="token punctuation">;</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h4 id="客户端-2-3" tabindex="-1"><a class="header-anchor" href="#客户端-2-3"><span>客户端 2...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">const</span> plan <span class="token operator">=</span></span>
<span class="line">  aCustomer <span class="token operator">===</span> <span class="token string">"unknown"</span> <span class="token operator">?</span> registry<span class="token punctuation">.</span>billingPlans<span class="token punctuation">.</span>basic <span class="token operator">:</span> aCustomer<span class="token punctuation">.</span>billingPlan<span class="token punctuation">;</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div></div></div><h4 id="客户端-3-3" tabindex="-1"><a class="header-anchor" href="#客户端-3-3"><span>客户端 3...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">const</span> weeksDelinquent <span class="token operator">=</span></span>
<span class="line">  aCustomer <span class="token operator">===</span> <span class="token string">"unknown"</span></span>
<span class="line">    <span class="token operator">?</span> <span class="token number">0</span></span>
<span class="line">    <span class="token operator">:</span> aCustomer<span class="token punctuation">.</span>paymentHistory<span class="token punctuation">.</span>weeksDelinquentInLastYear<span class="token punctuation">;</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>和前面的例子一样，我首先在 Customer 中添加 isUnknown 属性，并创建一个包含同名字段的特例对象。这次的区别在于，特例对象是一个字面量。</p>
<h4 id="class-customer-3" tabindex="-1"><a class="header-anchor" href="#class-customer-3"><span>class Customer...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">get</span> <span class="token function">isUnknown</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><h4 id="顶层作用域" tabindex="-1"><a class="header-anchor" href="#顶层作用域"><span>顶层作用域...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">function</span> <span class="token function">createUnknownCustomer</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token keyword">return</span> <span class="token punctuation">{</span></span>
<span class="line">    <span class="token literal-property property">isUnknown</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span></span>
<span class="line">  <span class="token punctuation">}</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>然后我对检查特例的条件逻辑运用提炼函数（106）。</p>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">function</span> <span class="token function">isUnknown</span><span class="token punctuation">(</span><span class="token parameter">arg</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token keyword">return</span> arg <span class="token operator">===</span> <span class="token string">"unknown"</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h4 id="客户端-1-6" tabindex="-1"><a class="header-anchor" href="#客户端-1-6"><span>客户端 1...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">let</span> customerName<span class="token punctuation">;</span></span>
<span class="line"><span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">isUnknown</span><span class="token punctuation">(</span>aCustomer<span class="token punctuation">)</span><span class="token punctuation">)</span> customerName <span class="token operator">=</span> <span class="token string">"occupant"</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token keyword">else</span> customerName <span class="token operator">=</span> aCustomer<span class="token punctuation">.</span>name<span class="token punctuation">;</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h4 id="客户端-2-4" tabindex="-1"><a class="header-anchor" href="#客户端-2-4"><span>客户端 2...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">const</span> plan <span class="token operator">=</span> <span class="token function">isUnknown</span><span class="token punctuation">(</span>aCustomer<span class="token punctuation">)</span></span>
<span class="line">  <span class="token operator">?</span> registry<span class="token punctuation">.</span>billingPlans<span class="token punctuation">.</span>basic</span>
<span class="line">  <span class="token operator">:</span> aCustomer<span class="token punctuation">.</span>billingPlan<span class="token punctuation">;</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h4 id="客户端-3-4" tabindex="-1"><a class="header-anchor" href="#客户端-3-4"><span>客户端 3...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">const</span> weeksDelinquent <span class="token operator">=</span> <span class="token function">isUnknown</span><span class="token punctuation">(</span>aCustomer<span class="token punctuation">)</span></span>
<span class="line">  <span class="token operator">?</span> <span class="token number">0</span></span>
<span class="line">  <span class="token operator">:</span> aCustomer<span class="token punctuation">.</span>paymentHistory<span class="token punctuation">.</span>weeksDelinquentInLastYear<span class="token punctuation">;</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>修改 Site 类和做条件判断的 isUnknown 函数，开始使用特例对象。</p>
<h4 id="class-site-3" tabindex="-1"><a class="header-anchor" href="#class-site-3"><span>class Site...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">get</span> <span class="token function">customer</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>_customer <span class="token operator">===</span> <span class="token string">"unknown"</span><span class="token punctuation">)</span> <span class="token operator">?</span> <span class="token function">createUnknownCustomer</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">:</span> <span class="token keyword">this</span><span class="token punctuation">.</span>_customer<span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h4 id="顶层作用域-1" tabindex="-1"><a class="header-anchor" href="#顶层作用域-1"><span>顶层作用域...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">function</span> <span class="token function">isUnknown</span><span class="token punctuation">(</span><span class="token parameter">arg</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token keyword">return</span> arg<span class="token punctuation">.</span>isUnknown<span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>然后把“以标准方式应对特例”的地方都替换成使用特例字面量的值。首先从“名字”开始：</p>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">function</span> <span class="token function">createUnknownCustomer</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token keyword">return</span> <span class="token punctuation">{</span></span>
<span class="line">    <span class="token literal-property property">isUnknown</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span></span>
<span class="line">    <span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">"occupant"</span><span class="token punctuation">,</span></span>
<span class="line">  <span class="token punctuation">}</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h4 id="客户端-1-7" tabindex="-1"><a class="header-anchor" href="#客户端-1-7"><span>客户端 1...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">const</span> customerName <span class="token operator">=</span> aCustomer<span class="token punctuation">.</span>name<span class="token punctuation">;</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><p>接着是“计价套餐”：</p>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">function</span> <span class="token function">createUnknownCustomer</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token keyword">return</span> <span class="token punctuation">{</span></span>
<span class="line">    <span class="token literal-property property">isUnknown</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span></span>
<span class="line">    <span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">"occupant"</span><span class="token punctuation">,</span></span>
<span class="line">    <span class="token literal-property property">billingPlan</span><span class="token operator">:</span> registry<span class="token punctuation">.</span>billingPlans<span class="token punctuation">.</span>basic<span class="token punctuation">,</span></span>
<span class="line">  <span class="token punctuation">}</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h4 id="客户端-2-5" tabindex="-1"><a class="header-anchor" href="#客户端-2-5"><span>客户端 2...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">const</span> plan <span class="token operator">=</span> aCustomer<span class="token punctuation">.</span>billingPlan<span class="token punctuation">;</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><p>同样，我可以在字面量对象中创建一个嵌套的空支付记录对象：</p>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">function</span> <span class="token function">createUnknownCustomer</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token keyword">return</span> <span class="token punctuation">{</span></span>
<span class="line">    <span class="token literal-property property">isUnknown</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span></span>
<span class="line">    <span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">"occupant"</span><span class="token punctuation">,</span></span>
<span class="line">    <span class="token literal-property property">billingPlan</span><span class="token operator">:</span> registry<span class="token punctuation">.</span>billingPlans<span class="token punctuation">.</span>basic<span class="token punctuation">,</span></span>
<span class="line">    <span class="token literal-property property">paymentHistory</span><span class="token operator">:</span> <span class="token punctuation">{</span></span>
<span class="line">      <span class="token literal-property property">weeksDelinquentInLastYear</span><span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">,</span></span>
<span class="line">    <span class="token punctuation">}</span><span class="token punctuation">,</span></span>
<span class="line">  <span class="token punctuation">}</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h4 id="客户端-3-5" tabindex="-1"><a class="header-anchor" href="#客户端-3-5"><span>客户端 3...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">const</span> weeksDelinquent <span class="token operator">=</span> aCustomer<span class="token punctuation">.</span>paymentHistory<span class="token punctuation">.</span>weeksDelinquentInLastYear<span class="token punctuation">;</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><p>如果使用了这样的字面量，应该使用诸如 Object.freeze 的方法将其冻结，使其不可变。通常，我还是喜欢用类多一点。</p>
<h3 id="范例-使用变换" tabindex="-1"><a class="header-anchor" href="#范例-使用变换"><span>范例：使用变换</span></a></h3>
<p>前面两个例子都涉及了一个类，其实本重构手法也同样适用于记录，只要增加一个变换步骤即可。</p>
<p>假设我们的输入是一个简单的记录结构，大概像这样：</p>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token punctuation">{</span></span>
<span class="line"> <span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">"Acme Boston"</span><span class="token punctuation">,</span></span>
<span class="line"> <span class="token literal-property property">location</span><span class="token operator">:</span> <span class="token string">"Malden MA"</span><span class="token punctuation">,</span></span>
<span class="line"> <span class="token comment">// more site details</span></span>
<span class="line"> <span class="token literal-property property">customer</span><span class="token operator">:</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">"Acme Industries"</span><span class="token punctuation">,</span></span>
<span class="line">  <span class="token literal-property property">billingPlan</span><span class="token operator">:</span> <span class="token string">"plan-451"</span><span class="token punctuation">,</span></span>
<span class="line">  <span class="token literal-property property">paymentHistory</span><span class="token operator">:</span> <span class="token punctuation">{</span></span>
<span class="line">   <span class="token literal-property property">weeksDelinquentInLastYear</span><span class="token operator">:</span> <span class="token number">7</span></span>
<span class="line">   <span class="token comment">//more</span></span>
<span class="line">  <span class="token punctuation">}</span><span class="token punctuation">,</span></span>
<span class="line">  <span class="token comment">// more</span></span>
<span class="line"> <span class="token punctuation">}</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>有时顾客的名字未知，此时标记的方式与前面一样：将 customer 字段标记为字符串&quot;unknown&quot;。</p>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token punctuation">{</span></span>
<span class="line"><span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">"Warehouse Unit 15"</span><span class="token punctuation">,</span></span>
<span class="line"><span class="token literal-property property">location</span><span class="token operator">:</span> <span class="token string">"Malden MA"</span><span class="token punctuation">,</span></span>
<span class="line"><span class="token comment">// more site details</span></span>
<span class="line"><span class="token literal-property property">customer</span><span class="token operator">:</span> <span class="token string">"unknown"</span><span class="token punctuation">,</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>客户端代码也类似，会检查“未知顾客”的情况：</p>
<h4 id="客户端-1-8" tabindex="-1"><a class="header-anchor" href="#客户端-1-8"><span>客户端 1...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">const</span> site <span class="token operator">=</span> <span class="token function">acquireSiteData</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token keyword">const</span> aCustomer <span class="token operator">=</span> site<span class="token punctuation">.</span>customer<span class="token punctuation">;</span></span>
<span class="line"><span class="token comment">// ... lots of intervening code ...</span></span>
<span class="line"><span class="token keyword">let</span> customerName<span class="token punctuation">;</span></span>
<span class="line"><span class="token keyword">if</span> <span class="token punctuation">(</span>aCustomer <span class="token operator">===</span> <span class="token string">"unknown"</span><span class="token punctuation">)</span> customerName <span class="token operator">=</span> <span class="token string">"occupant"</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token keyword">else</span> customerName <span class="token operator">=</span> aCustomer<span class="token punctuation">.</span>name<span class="token punctuation">;</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h4 id="客户端-2-6" tabindex="-1"><a class="header-anchor" href="#客户端-2-6"><span>客户端 2...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">const</span> plan <span class="token operator">=</span></span>
<span class="line">  aCustomer <span class="token operator">===</span> <span class="token string">"unknown"</span> <span class="token operator">?</span> registry<span class="token punctuation">.</span>billingPlans<span class="token punctuation">.</span>basic <span class="token operator">:</span> aCustomer<span class="token punctuation">.</span>billingPlan<span class="token punctuation">;</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div></div></div><h4 id="客户端-3-6" tabindex="-1"><a class="header-anchor" href="#客户端-3-6"><span>客户端 3...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">const</span> weeksDelinquent <span class="token operator">=</span></span>
<span class="line">  aCustomer <span class="token operator">===</span> <span class="token string">"unknown"</span></span>
<span class="line">    <span class="token operator">?</span> <span class="token number">0</span></span>
<span class="line">    <span class="token operator">:</span> aCustomer<span class="token punctuation">.</span>paymentHistory<span class="token punctuation">.</span>weeksDelinquentInLastYear<span class="token punctuation">;</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>我首先要让 Site 数据结构经过一次变换，目前变换中只做了深复制，没有对数据做任何处理。</p>
<h4 id="客户端-1-9" tabindex="-1"><a class="header-anchor" href="#客户端-1-9"><span>客户端 1...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">const</span> rawSite <span class="token operator">=</span> <span class="token function">acquireSiteData</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token keyword">const</span> site <span class="token operator">=</span> <span class="token function">enrichSite</span><span class="token punctuation">(</span>rawSite<span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token keyword">const</span> aCustomer <span class="token operator">=</span> site<span class="token punctuation">.</span>customer<span class="token punctuation">;</span></span>
<span class="line"><span class="token comment">// ... lots of intervening code ...</span></span>
<span class="line"><span class="token keyword">let</span> customerName<span class="token punctuation">;</span></span>
<span class="line"><span class="token keyword">if</span> <span class="token punctuation">(</span>aCustomer <span class="token operator">===</span> <span class="token string">"unknown"</span><span class="token punctuation">)</span> customerName <span class="token operator">=</span> <span class="token string">"occupant"</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token keyword">else</span> customerName <span class="token operator">=</span> aCustomer<span class="token punctuation">.</span>name<span class="token punctuation">;</span></span>
<span class="line"></span>
<span class="line"><span class="token keyword">function</span> <span class="token function">enrichSite</span><span class="token punctuation">(</span><span class="token parameter">inputSite</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token keyword">return</span> _<span class="token punctuation">.</span><span class="token function">cloneDeep</span><span class="token punctuation">(</span>inputSite<span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>然后对“检查未知顾客”的代码运用提炼函数（106）。</p>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">function</span> <span class="token function">isUnknown</span><span class="token punctuation">(</span><span class="token parameter">aCustomer</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token keyword">return</span> aCustomer <span class="token operator">===</span> <span class="token string">"unknown"</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h4 id="客户端-1-10" tabindex="-1"><a class="header-anchor" href="#客户端-1-10"><span>客户端 1...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">const</span> rawSite <span class="token operator">=</span> <span class="token function">acquireSiteData</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token keyword">const</span> site <span class="token operator">=</span> <span class="token function">enrichSite</span><span class="token punctuation">(</span>rawSite<span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token keyword">const</span> aCustomer <span class="token operator">=</span> site<span class="token punctuation">.</span>customer<span class="token punctuation">;</span></span>
<span class="line"><span class="token comment">// ... lots of intervening code ...</span></span>
<span class="line"><span class="token keyword">let</span> customerName<span class="token punctuation">;</span></span>
<span class="line"><span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">isUnknown</span><span class="token punctuation">(</span>aCustomer<span class="token punctuation">)</span><span class="token punctuation">)</span> customerName <span class="token operator">=</span> <span class="token string">"occupant"</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token keyword">else</span> customerName <span class="token operator">=</span> aCustomer<span class="token punctuation">.</span>name<span class="token punctuation">;</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h4 id="客户端-2-7" tabindex="-1"><a class="header-anchor" href="#客户端-2-7"><span>客户端 2...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">const</span> plan <span class="token operator">=</span> <span class="token function">isUnknown</span><span class="token punctuation">(</span>aCustomer<span class="token punctuation">)</span></span>
<span class="line">  <span class="token operator">?</span> registry<span class="token punctuation">.</span>billingPlans<span class="token punctuation">.</span>basic</span>
<span class="line">  <span class="token operator">:</span> aCustomer<span class="token punctuation">.</span>billingPlan<span class="token punctuation">;</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h4 id="客户端-3-7" tabindex="-1"><a class="header-anchor" href="#客户端-3-7"><span>客户端 3...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">const</span> weeksDelinquent <span class="token operator">=</span> <span class="token function">isUnknown</span><span class="token punctuation">(</span>aCustomer<span class="token punctuation">)</span></span>
<span class="line">  <span class="token operator">?</span> <span class="token number">0</span></span>
<span class="line">  <span class="token operator">:</span> aCustomer<span class="token punctuation">.</span>paymentHistory<span class="token punctuation">.</span>weeksDelinquentInLastYear<span class="token punctuation">;</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>然后开始对 Site 数据做增强，首先是给 customer 字段加上 isUnknown 属性。</p>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">function</span> <span class="token function">enrichSite</span><span class="token punctuation">(</span><span class="token parameter">aSite</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token keyword">const</span> result <span class="token operator">=</span> _<span class="token punctuation">.</span><span class="token function">cloneDeep</span><span class="token punctuation">(</span>aSite<span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">const</span> unknownCustomer <span class="token operator">=</span> <span class="token punctuation">{</span></span>
<span class="line">    <span class="token literal-property property">isUnknown</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span></span>
<span class="line">  <span class="token punctuation">}</span><span class="token punctuation">;</span></span>
<span class="line"></span>
<span class="line">  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">isUnknown</span><span class="token punctuation">(</span>result<span class="token punctuation">.</span>customer<span class="token punctuation">)</span><span class="token punctuation">)</span> result<span class="token punctuation">.</span>customer <span class="token operator">=</span> unknownCustomer<span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">else</span> result<span class="token punctuation">.</span>customer<span class="token punctuation">.</span>isUnknown <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">return</span> result<span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>随后修改检查特例的条件逻辑，开始使用新的属性。原来的检查逻辑也保留不动，所以现在的检查逻辑应该既能应对原来的 Site 数据，也能应对增强后的 Site 数据。</p>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">function</span> <span class="token function">isUnknown</span><span class="token punctuation">(</span><span class="token parameter">aCustomer</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token keyword">if</span> <span class="token punctuation">(</span>aCustomer <span class="token operator">===</span> <span class="token string">"unknown"</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">else</span> <span class="token keyword">return</span> aCustomer<span class="token punctuation">.</span>isUnknown<span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>测试，确保一切正常，然后针对特例使用函数组合成变换（149）。首先把“未知顾客的名字”的处理逻辑搬进增强函数。</p>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">function</span> <span class="token function">enrichSite</span><span class="token punctuation">(</span><span class="token parameter">aSite</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token keyword">const</span> result <span class="token operator">=</span> _<span class="token punctuation">.</span><span class="token function">cloneDeep</span><span class="token punctuation">(</span>aSite<span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">const</span> unknownCustomer <span class="token operator">=</span> <span class="token punctuation">{</span></span>
<span class="line">    <span class="token literal-property property">isUnknown</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span></span>
<span class="line">    <span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">"occupant"</span><span class="token punctuation">,</span></span>
<span class="line">  <span class="token punctuation">}</span><span class="token punctuation">;</span></span>
<span class="line"></span>
<span class="line">  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">isUnknown</span><span class="token punctuation">(</span>result<span class="token punctuation">.</span>customer<span class="token punctuation">)</span><span class="token punctuation">)</span> result<span class="token punctuation">.</span>customer <span class="token operator">=</span> unknownCustomer<span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">else</span> result<span class="token punctuation">.</span>customer<span class="token punctuation">.</span>isUnknown <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">return</span> result<span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h4 id="客户端-1-11" tabindex="-1"><a class="header-anchor" href="#客户端-1-11"><span>客户端 1...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">const</span> rawSite <span class="token operator">=</span> <span class="token function">acquireSiteData</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token keyword">const</span> site <span class="token operator">=</span> <span class="token function">enrichSite</span><span class="token punctuation">(</span>rawSite<span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token keyword">const</span> aCustomer <span class="token operator">=</span> site<span class="token punctuation">.</span>customer<span class="token punctuation">;</span></span>
<span class="line"><span class="token comment">// ... lots of intervening code ...</span></span>
<span class="line"><span class="token keyword">const</span> customerName <span class="token operator">=</span> aCustomer<span class="token punctuation">.</span>name<span class="token punctuation">;</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>测试，然后是“未知顾客的计价套餐”的处理逻辑。</p>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">function</span> <span class="token function">enrichSite</span><span class="token punctuation">(</span><span class="token parameter">aSite</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token keyword">const</span> result <span class="token operator">=</span> _<span class="token punctuation">.</span><span class="token function">cloneDeep</span><span class="token punctuation">(</span>aSite<span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">const</span> unknownCustomer <span class="token operator">=</span> <span class="token punctuation">{</span></span>
<span class="line">    <span class="token literal-property property">isUnknown</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span></span>
<span class="line">    <span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">"occupant"</span><span class="token punctuation">,</span></span>
<span class="line">    <span class="token literal-property property">billingPlan</span><span class="token operator">:</span> registry<span class="token punctuation">.</span>billingPlans<span class="token punctuation">.</span>basic<span class="token punctuation">,</span></span>
<span class="line">  <span class="token punctuation">}</span><span class="token punctuation">;</span></span>
<span class="line"></span>
<span class="line">  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">isUnknown</span><span class="token punctuation">(</span>result<span class="token punctuation">.</span>customer<span class="token punctuation">)</span><span class="token punctuation">)</span> result<span class="token punctuation">.</span>customer <span class="token operator">=</span> unknownCustomer<span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">else</span> result<span class="token punctuation">.</span>customer<span class="token punctuation">.</span>isUnknown <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">return</span> result<span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h4 id="客户端-2-8" tabindex="-1"><a class="header-anchor" href="#客户端-2-8"><span>客户端 2...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">const</span> plan <span class="token operator">=</span> aCustomer<span class="token punctuation">.</span>billingPlan<span class="token punctuation">;</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><p>再次测试，然后处理最后一处客户端代码。</p>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">function</span> <span class="token function">enrichSite</span><span class="token punctuation">(</span><span class="token parameter">aSite</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token keyword">const</span> result <span class="token operator">=</span> _<span class="token punctuation">.</span><span class="token function">cloneDeep</span><span class="token punctuation">(</span>aSite<span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">const</span> unknownCustomer <span class="token operator">=</span> <span class="token punctuation">{</span></span>
<span class="line">    <span class="token literal-property property">isUnknown</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span></span>
<span class="line">    <span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">"occupant"</span><span class="token punctuation">,</span></span>
<span class="line">    <span class="token literal-property property">billingPlan</span><span class="token operator">:</span> registry<span class="token punctuation">.</span>billingPlans<span class="token punctuation">.</span>basic<span class="token punctuation">,</span></span>
<span class="line">    <span class="token literal-property property">paymentHistory</span><span class="token operator">:</span> <span class="token punctuation">{</span></span>
<span class="line">      <span class="token literal-property property">weeksDelinquentInLastYear</span><span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">,</span></span>
<span class="line">    <span class="token punctuation">}</span><span class="token punctuation">,</span></span>
<span class="line">  <span class="token punctuation">}</span><span class="token punctuation">;</span></span>
<span class="line"></span>
<span class="line">  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">isUnknown</span><span class="token punctuation">(</span>result<span class="token punctuation">.</span>customer<span class="token punctuation">)</span><span class="token punctuation">)</span> result<span class="token punctuation">.</span>customer <span class="token operator">=</span> unknownCustomer<span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">else</span> result<span class="token punctuation">.</span>customer<span class="token punctuation">.</span>isUnknown <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">return</span> result<span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h4 id="客户端-3-8" tabindex="-1"><a class="header-anchor" href="#客户端-3-8"><span>客户端 3...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">const</span> weeksDelinquent <span class="token operator">=</span> aCustomer<span class="token punctuation">.</span>paymentHistory<span class="token punctuation">.</span>weeksDelinquentInLastYear<span class="token punctuation">;</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><h2 id="_10-6-引入断言-introduce-assertion" tabindex="-1"><a class="header-anchor" href="#_10-6-引入断言-introduce-assertion"><span>10.6 引入断言（Introduce Assertion）</span></a></h2>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line">  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>discountRate<span class="token punctuation">)</span></span>
<span class="line">  base <span class="token operator">=</span> base <span class="token operator">-</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>discountRate <span class="token operator">*</span> base<span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line">  <span class="token function">assert</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>discountRate<span class="token operator">>=</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>discountRate<span class="token punctuation">)</span></span>
<span class="line">  base <span class="token operator">=</span> base <span class="token operator">-</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>discountRate <span class="token operator">*</span> base<span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3 id="动机-5" tabindex="-1"><a class="header-anchor" href="#动机-5"><span>动机</span></a></h3>
<p>常常会有这样一段代码：只有当某个条件为真时，该段代码才能正常运行。例如，平方根计算只对正值才能进行，又例如，某个对象可能假设一组字段中至少有一个不等于 null。</p>
<p>这样的假设通常并没有在代码中明确表现出来，你必须阅读整个算法才能看出。有时程序员会以注释写出这样的假设，而我要介绍的是一种更好的技术——使用断言明确标明这些假设。</p>
<p>断言是一个条件表达式，应该总是为真。如果它失败，表示程序员犯了错误。断言的失败不应该被系统任何地方捕捉。整个程序的行为在有没有断言出现的时候都应该完全一样。实际上，有些编程语言中的断言可以在编译期用一个开关完全禁用掉。</p>
<p>我常看见有人鼓励用断言来发现程序中的错误。这固然是一件好事，但却不是使用断言的唯一理由。断言是一种很有价值的交流形式——它们告诉阅读者，程序在执行到这一点时，对当前状态做了何种假设。另外断言对调试也很有帮助。而且，因为它们在交流上很有价值，即使解决了当下正在追踪的错误，我还是倾向于把断言留着。自测试的代码降低了断言在调试方面的价值，因为逐步逼近的单元测试通常能更好地帮助调试，但我仍然看重断言在交流方面的价值。</p>
<h3 id="做法-5" tabindex="-1"><a class="header-anchor" href="#做法-5"><span>做法</span></a></h3>
<p>如果你发现代码假设某个条件始终为真，就加入一个断言明确说明这种情况。</p>
<p>因为断言应该不会对系统运行造成任何影响，所以“加入断言”永远都应该是行为保持的。</p>
<h3 id="范例-5" tabindex="-1"><a class="header-anchor" href="#范例-5"><span>范例</span></a></h3>
<p>下面是一个简单的例子：折扣。顾客（customer）会获得一个折扣率（discount rate），可以用于所有其购买的商品。</p>
<h4 id="class-customer-4" tabindex="-1"><a class="header-anchor" href="#class-customer-4"><span>class Customer...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token function">applyDiscount</span><span class="token punctuation">(</span><span class="token parameter">aNumber</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>discountRate<span class="token punctuation">)</span></span>
<span class="line">    <span class="token operator">?</span> aNumber <span class="token operator">-</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>discountRate <span class="token operator">*</span> aNumber<span class="token punctuation">)</span></span>
<span class="line">    <span class="token operator">:</span> aNumber<span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>这里有一个假设：折扣率永远是正数。我可以用断言明确标示出这个假设。但在一个三元表达式中没办法很简单地插入断言，所以我首先要把这个表达式转换成 if-else 的形式。</p>
<h4 id="class-customer-5" tabindex="-1"><a class="header-anchor" href="#class-customer-5"><span>class Customer...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token function">applyDiscount</span><span class="token punctuation">(</span><span class="token parameter">aNumber</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token keyword">this</span><span class="token punctuation">.</span>discountRate<span class="token punctuation">)</span> <span class="token keyword">return</span> aNumber<span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">else</span> <span class="token keyword">return</span> aNumber <span class="token operator">-</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>discountRate <span class="token operator">*</span> aNumber<span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>现在我就可以轻松地加入断言了。</p>
<h4 id="class-customer-6" tabindex="-1"><a class="header-anchor" href="#class-customer-6"><span>class Customer...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token function">applyDiscount</span><span class="token punctuation">(</span><span class="token parameter">aNumber</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token keyword">this</span><span class="token punctuation">.</span>discountRate<span class="token punctuation">)</span> <span class="token keyword">return</span> aNumber<span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">else</span> <span class="token punctuation">{</span></span>
<span class="line">    <span class="token function">assert</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>discountRate <span class="token operator">>=</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line">    <span class="token keyword">return</span> aNumber <span class="token operator">-</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>discountRate <span class="token operator">*</span> aNumber<span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token punctuation">}</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>对这个例子而言，我更愿意把断言放在设值函数上。如果在 applyDiscount 函数处发生断言失败，我还得先费力搞清楚非法的折扣率值起初是从哪儿放进去的。</p>
<h4 id="class-customer-7" tabindex="-1"><a class="header-anchor" href="#class-customer-7"><span>class Customer...</span></a></h4>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line"><span class="token keyword">set</span> <span class="token function">discountRate</span><span class="token punctuation">(</span><span class="token parameter">aNumber</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">  <span class="token function">assert</span><span class="token punctuation">(</span><span class="token keyword">null</span> <span class="token operator">===</span> aNumber <span class="token operator">||</span> aNumber <span class="token operator">>=</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line">  <span class="token keyword">this</span><span class="token punctuation">.</span>_discountRate <span class="token operator">=</span> aNumber<span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>真正引起错误的源头有可能很难发现——也许是输入数据中误写了一个减号，也许是某处代码做数据转换时犯了错误。像这样的断言对于发现错误源头特别有帮助。</p>
<p>注意，不要滥用断言。我不会使用断言来检查所有“我认为应该为真”的条件，只用来检查“必须为真”的条件。滥用断言可能会造成代码重复，尤其是在处理上面这样的条件逻辑时。所以我发现，很有必要去掉条件逻辑中的重复，通常可以借助提炼函数（106）手法。</p>
<p>我只用断言预防程序员的错误。如果要从某个外部数据源读取数据，那么所有对输入值的检查都应该是程序的一等公民，而不能用断言实现——除非我对这个外部数据源有绝对的信心。断言是帮助我们跟踪 bug 的最后一招，所以，或许听来讽刺，只有当我认为断言绝对不会失败的时候，我才会使用断言。</p>
</div></template>


